• 数据结构--排序


    前言

            我们的生活也是离不开排序的,比如我们集合排队,通常情况老师会按照从高到低排,这不不仅仅是为了方队好看,也是让后面的同学能够看见前面事物。还有,当学校按照综合成绩的排名来挑出前10名同学来评国家奖学金。生活中的排序是为了解决一些实际问题,那么在数据结构中,排序是将一组数据按照规定的顺序重新排列,其目的是为了便于查询和处理数据。排序中关键操作为比较和交换。时间性能是我们区分排序算法的好坏的主要因数。

            该文章我们会详细讲解常见排序算法--直接插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序。我们的任务是从理解每个排序的思想到能够实现每个排序的代码,在实现过程中我们主要观察动图来理解排序思路,通过比较它的时间性能我们深刻的理解各个排序的好坏。你也许想问:怎么多排序那个更重要?那个更难理解?有些时候事情也会反过来,我们根据特定排序算法来选择数据结构与之适应。不论那种排序都是重要的。数据结构本来就是比较抽象,难以理解的,但是相信循序渐进的学习是会将它们一一解决,这里特别需要注意的是快速排序,它的三个方法,两种实现都也会一一讲解,所以切勿担心。开始正文之前,我想分享一句话:一个人幸运的前提,其实是他有能力改变自己。这里我想激励自己的同时也想与大家一起前行。

    目录

    前言

    排序的概念及其运用

    排序的概念

    排序运用

    插入排序

    直接插入排序   

    希尔排序

     选择排序

    直接选择排序

    堆排序

    交换排序

    冒泡排序 

    快速排序

    快速排序递归实现

    快速排序非递归实现

    归并排序

    排序算法复杂度及稳定性分析

    时间性能分析 


    排序的概念及其运用

    排序的概念

    排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

    稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

    内部排序:数据元素全部放在内存中的排序。

    外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

    排序运用

            我们在网上买东西的时候,它有综合,销量,评论数,新品(日期),价格等等进行排序,为了让用户更加容易找到适合自己的商品,这里我们以在京东买笔记本电脑举例。当我们选择综合排序时:

     当我们选择销量时:

             还有各个高校的排名,这里只是仅是用于参考排序的运用,如果排名有争议请不要过于纠结和过于在意。因为不同机构他有不同的排名机制。

    插入排序

            把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

    直接插入排序   

            直接插入排序把要排序的序列分成两部分:第一部分是有序子序列,而第二部分是无序序列。每次挑选无序序列中的第一个元素与第一部分的有序子序列从后往前逐个比较,当待排元素大于子序列中的元素时,将其插入到该元素的后方。直接插入排序会使用一个 "哨兵",用于存放元素。直接插入排序每次插入一个元素,所以排序趟数固定为 n-1。

    动态演示:

    代码实现:     

    1. // 插入排序
    2. void InsertSort(int* a, int n)
    3. {
    4. for (int i = 0; i < n - 1; i++)
    5. {
    6. int end=i;
    7. int tem = a[end + 1];
    8. while (end >= 0)
    9. {
    10. if (a[end] > tem)
    11. {
    12. a[end + 1] = a[end];
    13. end--;
    14. }
    15. else
    16. break;
    17. }
    18. a[end + 1] = tem;
    19. }
    20. }

    直接插入排序的特性总结:

    1. 元素集合越接近有序,直接插入排序算法的时间效率越高

    2. 时间复杂度:O(N^2)

    3. 空间复杂度:O(1),它是一种稳定的排序算法

    4. 稳定性:稳定

    希尔排序

            希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

    动态演示:

     代码实现:

    1. // 希尔排序
    2. void ShellSort(int* a, int n)
    3. {
    4. int gap = n;
    5. while (gap>1)
    6. {
    7. gap = gap / 3 + 1;
    8. for (int i = 0; i < gap; i++)
    9. {
    10. for (int j = i; j< n-gap; j += gap)
    11. {
    12. int end = j;
    13. int tem = a[end + gap];
    14. while (end <= 0)
    15. {
    16. if (a[end] > tem)
    17. {
    18. a[end + gap] = a[end];
    19. end -= gap;
    20. }
    21. else
    22. break;
    23. }
    24. a[end + gap] = tem;
    25. }
    26. }
    27. }
    28. }
    29. // 希尔排序(化简)
    30. void ShellSort(int* a, int n)
    31. {
    32. int gap = n;
    33. while (gap>1)
    34. {
    35. gap = gap / 3 + 1;
    36. for (int i = 0; i < n-gap; i++)
    37. {
    38. int end = i;
    39. int tem = a[end + gap];
    40. while (end <= 0)
    41. {
    42. if (a[end] > tem)
    43. {
    44. a[end + gap] = a[end];
    45. end -= gap;
    46. }
    47. else
    48. break;
    49. }
    50. a[end + gap] = tem;
    51. }
    52. }
    53. }

    希尔排序的特性总结:

    1. 希尔排序是对直接插入排序的优化。

    2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

    3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:

    《数据结构(C语言版)》 --- 严蔚敏

     《数据结构-用面相对象方法与C++描述》 --- 殷人昆

    因为咋们的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:

    4. 稳定性:不稳定

     选择排序

            每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完 。

    直接选择排序

            在元素集合array[i]--array[n-1 ]中选择关键码最大(小)的数据元素。若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换 。在剩余的array[i]--array[n-2(array[i+1 ]--array[n-1 ])集合中,重复上述步骤,直到集合剩余1个元素 。

    动态演示:

      代码实现:

    1. //交换
    2. void Swap(int *p1, int *p2)
    3. {
    4. int tem=*p1;
    5. *p1 = *p2;
    6. *p2 = tem;
    7. }
    8. // 选择排序
    9. void SelectSort(int* a, int n)
    10. {
    11. int begin = 0, end = n - 1;
    12. while (begin < end)
    13. {
    14. int min = begin, max = begin;
    15. for (int i = begin; i < end; i++)
    16. {
    17. if (a[min] < a[i])
    18. min = i;
    19. if (a[max]>a[i])
    20. max = i;
    21. }
    22. Swap(&a[min], &a[begin]);
    23. if (max == begin)
    24. max = min;
    25. Swap(&a[max], &a[end]);
    26. begin++;
    27. end--;
    28. }
    29. }

    直接选择排序的特性总结:

    1 . 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

    2. 时间复杂度: O(N^2)

    3. 空间复杂度: O(1 )

    4. 稳定性:不稳定

    堆排序

            堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

    动态演示:

     代码实现:

    1. void AdjustDown(int* a, int n, int parent)
    2. {
    3. int minChild = parent * 2 + 1;
    4. while (minChild < n)
    5. {
    6. // 找出小的那个孩子
    7. if (minChild + 1 < n && a[minChild + 1] > a[minChild])
    8. {
    9. minChild++;
    10. }
    11. if (a[minChild] > a[parent])
    12. {
    13. Swap(&a[minChild], &a[parent]);
    14. parent = minChild;
    15. minChild = parent * 2 + 1;
    16. }
    17. else
    18. {
    19. break;
    20. }
    21. }
    22. }
    23. // O(N*logN)
    24. void HeapSort(int* a, int n)
    25. {
    26. // 大思路:选择排序,依次选数,从后往前排
    27. // 升序 -- 大堆
    28. // 降序 -- 小堆
    29. // 建堆 -- 向下调整建堆 - O(N)
    30. for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    31. {
    32. AdjustDown(a, n, i);
    33. }
    34. // 选数 N*logN
    35. int i = 1;
    36. while (i < n)
    37. {
    38. Swap(&a[0], &a[n - i]);
    39. AdjustDown(a, n - i, 0);
    40. ++i;
    41. }
    42. }

    交换排序

            所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排 序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

    冒泡排序 

            重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。

    动态演示:

     代码实现:

    1. // 冒泡排序
    2. void BubbleSort(int* a, int n)
    3. {
    4. for (int i = 0; i < n; i++)
    5. {
    6. int flag = 0;
    7. for (int j = 1; j < n-i; j++)
    8. {
    9. if (a[j - 1] > a[j])
    10. Swap(&a[j -1], &a[j]);
    11. flag = 1;
    12. }
    13. if (flag == 0)
    14. break;
    15. }
    16. }

    冒泡排序的特性总结:  

    1 . 冒泡排序是一种非常容易理解的排序

    2. 时间复杂度: O(N^2)

    3. 空间复杂度: O(1 )

    4. 稳定性:稳定

    快速排序

            快速排序是Hoare于1 962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

    hoare版本

            确定key值,right先向左找比key小的值,找到停止。然后left向右找比key大值,找到与right交换。再次循环相同操作,直到他们相遇,将相遇值与key值交换,返回相遇值。

    动态演示:

     代码实现:

    1. // 快速排序hoare版本
    2. int PartSort1(int* a, int left, int right)
    3. {
    4. int mid = GetMidIndex(a, left, right);
    5. Swap(&a[left], &a[mid]);
    6. int keyi = left;
    7. while (left < right)
    8. {
    9. // R找小
    10. while (left < right && a[right] >= a[keyi])
    11. {
    12. --right;
    13. }
    14. // L找大
    15. while (left < right && a[left] <= a[keyi])
    16. {
    17. ++left;
    18. }
    19. if (left < right)
    20. Swap(&a[left], &a[right]);
    21. }
    22. int meeti = left;
    23. Swap(&a[meeti], &a[keyi]);
    24. return meeti;
    25. }

    挖坑法

            设置一个坑位,将坑位的值赋值为key,right向左移找比key小值,找到将值放入坑位。left向右移找比key大值,找到将值放入坑位。相继操作到相遇为止,当相遇时key值放入相遇坑位,最后返回该值。

    动态演示:

     代码实现:

    1. int PartSort2(int* a, int left, int right)
    2. {
    3. // 三数取中
    4. int mid = GetMidIndex(a, left, right);
    5. Swap(&a[left], &a[mid]);
    6. int key = a[left];
    7. int hole = left; //坑位
    8. while (left<right)
    9. {
    10. //右边先找小,填左坑位
    11. while (left < right&&a[right] >= a[key])
    12. {
    13. right--;
    14. }
    15. a[hole] = a[right];
    16. hole = right;
    17. //左边找大,填右坑位
    18. while (left < right&&a[left] <= a[key])
    19. {
    20. left++;
    21. }
    22. a[hole] = a[left];
    23. hole = left;
    24. }
    25. a[hole] = a[key];
    26. return hole;
    27. }

    前后指针

            在开始之前,prev与key位置相同,cur则在prev前面。这里主要分为两种情况:1.当cur的值小于key和prev的值也小于key时,prev先向前移动,cur再向前移动。2.当cur的值大于key值时,cur继续向后移,直到cur的值小于key值为止,然后prev再向前移动,直到prev的值大于key时,他们交换。直到cur大于right时,prve的值与key的值交换,最后跳出循环。

    动态演示:

     代码实现:

    1. // 快速排序前后指针法
    2. int PartSort3(int* a, int left, int right)
    3. {
    4. // 三数取中
    5. int mid = GetMidIndex(a, left, right);
    6. Swap(&a[left], &a[mid]);
    7. int key = left;
    8. int prev = left;
    9. int cur = left + 1;
    10. while (cur<=right)
    11. {
    12. //找小 prev先++ 相等跳出判断cur++,cur的值大于key值跳出判断cur++
    13. if (a[cur] < a[key] && ++prev != cur)
    14. Swap(&a[prev], &a[cur]);
    15. cur++;
    16. }
    17. Swap(&a[prev], &a[key]);
    18. return prev;
    19. }

            三个版本是都是单趟排序,我通过它的单趟排序观察出都是先确定key值,那么优选key的逻辑:1.随机选一个位置做key,2.针对有序,选正中间值做key,3.三数取中:通过比较第一个位置, 中间位置和最后一个位置,选取中间的位置即可。

            为什么需要三数取中呢?因为当一组数据是接近逆序时,我们发现right找小会将一组数据全部找完。这里我们hoare方法来举例:

    三数取中

    代码实现:

    1. //三数取中
    2. int GetMidIndex(int* a, int left, int right)
    3. {
    4. int mid = (left + right) / 2;
    5. if (a[left] < a[mid])
    6. {
    7. if (a[right] > a[mid])
    8. return mid;
    9. else if (a[left] < a[right])
    10. return left;
    11. else
    12. return right;
    13. }
    14. else
    15. {
    16. if (a[right]>a[left])
    17. return left;
    18. else if (a[right] < a[mid])
    19. return mid;
    20. else
    21. return right;
    22. }
    23. }

    快速排序递归实现

            上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,我们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

             在实现递归的时候,我们发现最后三层递归,占了全部递归次数的85%左右,当递归在最后三层的时候已经非常接近有序了,所以我们选择用直接插入。

     代码实现:

    1. // 快速排序递归实现
    2. void QuickSort(int* a, int left, int right)
    3. {
    4. if (left >= right)
    5. {
    6. return;
    7. }
    8. else if (right - left <= 8) //2^3 倒数三层(每次递归/2,递归三次 ),递归次数占接近85%
    9. {
    10. InsertSort(a + left, right - left + 1); //直接插入
    11. }
    12. else
    13. {
    14. int key = PartSort1(a, left, right);//key的作用:划分范围
    15. QuickSort(a, left, key - 1);//递归向左减少key
    16. QuickSort(a, key + 1, right);//递归向右增加key
    17. }
    18. }

    快速排序非递归实现

            这里非递归就将递归转化成非递归,通过栈(先进后出)的特征实现递归。因为递归就是操作系统的函数栈帧,我们可以模拟栈帧,在堆上开辟空间(malloc),赋予的功能。一个是操作系统的栈,一个是数据结构的栈。

            首先我们知道,在实现非递归之前我会用到数据结构中的栈,我们通过运用Stack.h,Stack.c构建的栈来实现快排的非递归。在这个过程中,我们需要联想到出栈入栈,寻找key值,始终记住这个过程只是通过栈将数据处理,排序的操作在单次排序进行。

    代码实现:

    1. // 快速排序 非递归实现
    2. void QuickSortNonR(int* a, int left, int right)
    3. {
    4. Stack st;
    5. StackInit(&st);
    6. StackPush(&st,left);
    7. StackPush(&st,right);
    8. while (!StackEmpty(&st))
    9. {
    10. left = StackTop(&st);
    11. StackPop(&st);
    12. right = StackTop(&st);
    13. StackPop(&st);
    14. int key = PartSort1(a, left, right);
    15. if (right > key+1)
    16. {
    17. StackPush(&st, key+1);
    18. StackPush(&st, right);
    19. }
    20. if (left < key-1)
    21. {
    22. StackPush(&st, left);
    23. StackPush(&st, key - 1);
    24. }
    25. }
    26. StackDestroy(&st);
    27. }

     快速排序的特性总结:

    1 . 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

    2. 时间复杂度: O(N*logN)

    3. 空间复杂度: O(logN)

    4. 稳定性:不稳定

    归并排序

            归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

    动态演示:

    递归

    代码实现: 

    1. void _MergeSort(int* a, int begin, int end, int* tmp)
    2. {
    3. if (begin >= end)
    4. return;
    5. int mid = (end + begin) / 2;
    6. _MergeSort(a, begin, mid, tmp);
    7. _MergeSort(a, mid + 1, end, tmp);
    8. // 归并 取小的尾插
    9. int begin1 = begin, end1 = mid;
    10. int begin2 = mid + 1, end2 = end;
    11. int i = begin;
    12. while (begin1 <= end1 && begin2 <= end2)
    13. {
    14. if (a[begin1] <= a[begin2])
    15. {
    16. tmp[i++] = a[begin1++];
    17. }
    18. else
    19. {
    20. tmp[i++] = a[begin2++];
    21. }
    22. }
    23. while (begin1 <= end1)
    24. {
    25. tmp[i++] = a[begin1++];
    26. }
    27. while (begin2 <= end2)
    28. {
    29. tmp[i++] = a[begin2++];
    30. }
    31. // 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
    32. memcpy(a + begin, tmp + begin, (end - begin + 1)*sizeof(int));
    33. }
    34. // 归并排序递归实现
    35. void MergeSort(int* a, int n)
    36. {
    37. int* temp = (int*)malloc(sizeof(int)*n);
    38. if (temp == NULL)
    39. {
    40. perror("malloc fail");
    41. exit(-1);
    42. }
    43. _MergeSort(a, 0, n-1, temp);
    44. free(temp);
    45. temp = NULL;
    46. }

     动态演示:

     非递归

    代码实现:

    1. // 归并排序非递归实现
    2. void MergeSortNonR(int* a, int n)
    3. {
    4. int* tmp = (int*)malloc(sizeof(int)*n);
    5. if (tmp == NULL)
    6. {
    7. perror("malloc fail");
    8. exit(-1);
    9. }
    10. int gap = 1;
    11. while (gap < n)
    12. {
    13. for (int j = 0; j < n; j += 2 * gap)
    14. {
    15. int begin1 = j, end1 = j + gap - 1;
    16. int begin2 = j + gap, end2 = j + 2 * gap - 1;
    17. //第一组end1越界
    18. if (end1>=n)
    19. {
    20. break;
    21. }
    22. //第二组全部越界
    23. if (begin2 >= n)
    24. {
    25. break;
    26. }
    27. //第二组部分越界
    28. if (end2 >= n)
    29. {
    30. end2 = n - 1;
    31. }
    32. int i = j;
    33. while (begin1 <= end1 && begin2 <= end2)
    34. {
    35. if (a[begin1] <= a[begin2])
    36. {
    37. tmp[i++] = a[begin1++];
    38. }
    39. else
    40. {
    41. tmp[i++] = a[begin2++];
    42. }
    43. }
    44. while (begin1 <= end1)
    45. {
    46. tmp[i++] = a[begin1++];
    47. }
    48. while (begin2 <= end2)
    49. {
    50. tmp[i++] = a[begin2++];
    51. }
    52. // 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
    53. memcpy(a + j, tmp + j, (end2 - j + 1)*sizeof(int));
    54. }
    55. gap *= 2;
    56. }
    57. free(tmp);
    58. tmp = NULL;
    59. }

    归并排序的特性总结:

    1 . 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

    2. 时间复杂度: O(N*logN)

    3. 空间复杂度: O(N)

    4. 稳定性:稳定

    排序算法复杂度及稳定性分析

     

    时间性能分析 

    Test.c文件

            我们在测试文件中,我们可以改变N,用于改变数据的多少,数据量越大则时间越久,我们可以通过不同的数据来观察各个排序所需时间。也能更直观的观察到时间复杂度,

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include "Sort.h"
    3. #include "Stack.h"
    4. // 测试排序的性能对比
    5. void TestOP()
    6. {
    7. srand(time(0));
    8. const int N = 100000;
    9. int* a1 = (int*)malloc(sizeof(int)*N);
    10. int* a2 = (int*)malloc(sizeof(int)*N);
    11. int* a3 = (int*)malloc(sizeof(int)*N);
    12. int* a4 = (int*)malloc(sizeof(int)*N);
    13. int* a5 = (int*)malloc(sizeof(int)*N);
    14. int* a6 = (int*)malloc(sizeof(int)*N);
    15. for (int i = 0; i < N; ++i)
    16. {
    17. a1[i] = rand();
    18. a2[i] = a1[i];
    19. a3[i] = a1[i];
    20. a4[i] = a1[i];
    21. a5[i] = a1[i];
    22. a6[i] = a1[i];
    23. }
    24. int begin1 = clock();
    25. InsertSort(a1, N);
    26. int end1 = clock();
    27. int begin2 = clock();
    28. ShellSort(a2, N);
    29. int end2 = clock();
    30. int begin3 = clock();
    31. SelectSort(a3, N);
    32. int end3 = clock();
    33. int begin4 = clock();
    34. HeapSort(a4, N);
    35. int end4 = clock();
    36. int begin5 = clock();
    37. QuickSort(a5, 0, N - 1);
    38. int end5 = clock();
    39. //int begin6 = clock();
    40. //MergeSort(a6, N);
    41. //int end6 = clock();
    42. printf("InsertSort:%d\n", end1 - begin1);
    43. printf("ShellSort:%d\n", end2 - begin2);
    44. printf("SelectSort:%d\n", end3 - begin3);
    45. printf("HeapSort:%d\n", end4 - begin4);
    46. printf("QuickSort:%d\n", end5 - begin5);
    47. //printf("MergeSort:%d\n", end6 - begin6);
    48. free(a1);
    49. free(a2);
    50. free(a3);
    51. free(a4);
    52. free(a5);
    53. //free(a6);
    54. }
    55. void TestInsertSort()
    56. {
    57. int a[] = { 34, 56, 25, 65, 86, 99, 72, 66 };
    58. InsertSort(a, sizeof(a) / sizeof(int));
    59. }
    60. void TestShellSort()
    61. {
    62. //int a[] = { 9,8,7,6,5,4,3,2,1,0 };
    63. int a[] = { 34, 56, 25, 65, 86, 99, 72, 66 };
    64. ShellSort(a, sizeof(a) / sizeof(int));
    65. PrintArray(a, sizeof(a) / sizeof(int));
    66. }
    67. void TestSelectSort()
    68. {
    69. int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
    70. SelectSort(a, sizeof(a) / sizeof(int));
    71. PrintArray(a, sizeof(a) / sizeof(int));
    72. }
    73. void TestHeapSort()
    74. {
    75. int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
    76. HeapSort(a, sizeof(a) / sizeof(int));
    77. PrintArray(a, sizeof(a) / sizeof(int));
    78. }
    79. void TestBubbleSort()
    80. {
    81. int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
    82. BubbleSort(a, sizeof(a) / sizeof(int));
    83. PrintArray(a, sizeof(a) / sizeof(int));
    84. }
    85. void TestQuickSort()
    86. {
    87. int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
    88. QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
    89. PrintArray(a, sizeof(a) / sizeof(int));
    90. }
    91. void TestQuickSortNonR()
    92. {
    93. int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
    94. QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
    95. PrintArray(a, sizeof(a) / sizeof(int));
    96. }
    97. int main()
    98. {
    99. //TestOP();
    100. //TestBubbleSort();
    101. //TestQuickSort();
    102. TestQuickSortNonR();
    103. return 0;
    104. }

    Sort.c文件

    1. #include "Sort.h"
    2. #include "Stack.h"
    3. //交换
    4. void Swap(int *p1, int *p2)
    5. {
    6. int tem=*p1;
    7. *p1 = *p2;
    8. *p2 = tem;
    9. }
    10. void PrintArray(int* a,int n)
    11. {
    12. for (int i = 0; i < n; i++)
    13. {
    14. printf("%d ", a[i]);
    15. }
    16. printf("\n");
    17. }
    18. // 插入排序
    19. void InsertSort(int* a, int n)
    20. {
    21. for (int i = 0; i < n - 1; i++)
    22. {
    23. int end=i;
    24. int tem = a[end + 1];
    25. while (end >= 0)
    26. {
    27. if (a[end] > tem)
    28. {
    29. a[end + 1] = a[end];
    30. end--;
    31. }
    32. else
    33. break;
    34. }
    35. a[end + 1] = tem;
    36. }
    37. }
    38. 希尔排序
    39. //void ShellSort(int* a, int n)
    40. //{
    41. // int gap = n;
    42. // while (gap>1)
    43. // {
    44. // gap = gap / 3 + 1;
    45. // for (int i = 0; i < gap; i++)
    46. // {
    47. // for (int j = i; j< n-gap; j += gap)
    48. // {
    49. // int end = j;
    50. // int tem = a[end + gap];
    51. // while (end <= 0)
    52. // {
    53. // if (a[end] > tem)
    54. // {
    55. // a[end + gap] = a[end];
    56. // end -= gap;
    57. // }
    58. // else
    59. // break;
    60. // }
    61. // a[end + gap] = tem;
    62. // }
    63. // }
    64. //
    65. // }
    66. //
    67. //}
    68. //
    69. // 希尔排序(化简)
    70. void ShellSort(int* a, int n)
    71. {
    72. int gap = n;
    73. while (gap>1)
    74. {
    75. gap = gap / 3 + 1;
    76. for (int i = 0; i < n-gap; i++)
    77. {
    78. int end = i;
    79. int tem = a[end + gap];
    80. while (end <= 0)
    81. {
    82. if (a[end] > tem)
    83. {
    84. a[end + gap] = a[end];
    85. end -= gap;
    86. }
    87. else
    88. break;
    89. }
    90. a[end + gap] = tem;
    91. }
    92. }
    93. }
    94. // 选择排序
    95. void SelectSort(int* a, int n)
    96. {
    97. int begin = 0, end = n - 1;
    98. while (begin < end)
    99. {
    100. int min = begin, max = begin;
    101. for (int i = begin; i < end; i++)
    102. {
    103. if (a[min] < a[i])
    104. min = i;
    105. if (a[max]>a[i])
    106. max = i;
    107. }
    108. Swap(&a[min], &a[begin]);
    109. if (max == begin)
    110. max = min;
    111. Swap(&a[max], &a[end]);
    112. begin++;
    113. end--;
    114. }
    115. }
    116. void AdjustDown(int* a, int n, int parent)
    117. {
    118. int minChild = parent * 2 + 1;
    119. while (minChild < n)
    120. {
    121. // 找出小的那个孩子
    122. if (minChild + 1 < n && a[minChild + 1] > a[minChild])
    123. {
    124. minChild++;
    125. }
    126. if (a[minChild] > a[parent])
    127. {
    128. Swap(&a[minChild], &a[parent]);
    129. parent = minChild;
    130. minChild = parent * 2 + 1;
    131. }
    132. else
    133. {
    134. break;
    135. }
    136. }
    137. }
    138. // O(N*logN)
    139. void HeapSort(int* a, int n)
    140. {
    141. // 大思路:选择排序,依次选数,从后往前排
    142. // 升序 -- 大堆
    143. // 降序 -- 小堆
    144. // 建堆 -- 向下调整建堆 - O(N)
    145. for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    146. {
    147. AdjustDown(a, n, i);
    148. }
    149. // 选数 N*logN
    150. int i = 1;
    151. while (i < n)
    152. {
    153. Swap(&a[0], &a[n - i]);
    154. AdjustDown(a, n - i, 0);
    155. ++i;
    156. }
    157. }
    158. // 冒泡排序
    159. void BubbleSort(int* a, int n)
    160. {
    161. for (int i = 0; i < n; i++)
    162. {
    163. int flag = 0;
    164. for (int j = 1; j < n-i; j++)
    165. {
    166. if (a[j - 1] > a[j])
    167. Swap(&a[j -1], &a[j]);
    168. flag = 1;
    169. }
    170. if (flag == 0)
    171. break;
    172. }
    173. }
    174. //三数取中
    175. int GetMidIndex(int* a, int left, int right)
    176. {
    177. int mid = (left + right) / 2;
    178. if (a[left] < a[mid])
    179. {
    180. if (a[right] > a[mid])
    181. return mid;
    182. else if (a[left] < a[right])
    183. return left;
    184. else
    185. return right;
    186. }
    187. else
    188. {
    189. if (a[right]>a[left])
    190. return left;
    191. else if (a[right] < a[mid])
    192. return mid;
    193. else
    194. return right;
    195. }
    196. }
    197. // 快速排序hoare版本
    198. int PartSort1(int* a, int left, int right)
    199. {
    200. int mid = GetMidIndex(a, left, right);
    201. Swap(&a[left], &a[mid]);
    202. int keyi = left;
    203. while (left < right)
    204. {
    205. // R找小
    206. while (left < right && a[right] >= a[keyi])
    207. {
    208. --right;
    209. }
    210. // L找大
    211. while (left < right && a[left] <= a[keyi])
    212. {
    213. ++left;
    214. }
    215. if (left < right)
    216. Swap(&a[left], &a[right]);
    217. }
    218. int meeti = left;
    219. Swap(&a[meeti], &a[keyi]);
    220. return meeti;
    221. }
    222. // 挖坑法
    223. int PartSort2(int* a, int left, int right)
    224. {
    225. // 三数取中
    226. int mid = GetMidIndex(a, left, right);
    227. Swap(&a[left], &a[mid]);
    228. int key = a[left];
    229. int hole = left; //坑位
    230. while (left<right)
    231. {
    232. //右边先找小,填左坑位
    233. while (left < right&&a[right] >= a[key])
    234. {
    235. right--;
    236. }
    237. a[hole] = a[right];
    238. hole = right;
    239. //左边找大,填右坑位
    240. while (left < right&&a[left] <= a[key])
    241. {
    242. left++;
    243. }
    244. a[hole] = a[left];
    245. hole = left;
    246. }
    247. a[hole] = a[key];
    248. return hole;
    249. }
    250. // 快速排序前后指针法
    251. int PartSort3(int* a, int left, int right)
    252. {
    253. // 三数取中
    254. int mid = GetMidIndex(a, left, right);
    255. Swap(&a[left], &a[mid]);
    256. int key = left;
    257. int prev = left;
    258. int cur = left + 1;
    259. while (cur<=right)
    260. {
    261. //找小 prev先++ 相等跳出判断cur++,cur的值大于key值跳出判断cur++
    262. if (a[cur] < a[key] && ++prev != cur)
    263. Swap(&a[prev], &a[cur]);
    264. cur++;
    265. }
    266. Swap(&a[prev], &a[key]);
    267. return prev;
    268. }
    269. // 快速排序递归实现
    270. void QuickSort(int* a, int left, int right)
    271. {
    272. if (left >= right)
    273. {
    274. return;
    275. }
    276. else if (right - left <= 8) //2^3 倒数三层(每次递归/2,递归三次 ),递归次数占接近85%
    277. {
    278. InsertSort(a + left, right - left + 1); //直接插入
    279. }
    280. else
    281. {
    282. int key = PartSort1(a, left, right);//key的作用:划分范围
    283. QuickSort(a, left, key - 1);//递归向左减少key
    284. QuickSort(a, key + 1, right);//递归向右增加key
    285. }
    286. }
    287. // 快速排序 非递归实现
    288. void QuickSortNonR(int* a, int left, int right)
    289. {
    290. Stack st;
    291. StackInit(&st);
    292. StackPush(&st,left);
    293. StackPush(&st,right);
    294. while (!StackEmpty(&st))
    295. {
    296. left = StackTop(&st);
    297. StackPop(&st);
    298. right = StackTop(&st);
    299. StackPop(&st);
    300. int key = PartSort1(a, left, right);
    301. if (right > key+1)
    302. {
    303. StackPush(&st, key+1);
    304. StackPush(&st, right);
    305. }
    306. if (left < key-1)
    307. {
    308. StackPush(&st, left);
    309. StackPush(&st, key - 1);
    310. }
    311. }
    312. StackDestroy(&st);
    313. }

    Sort.h文件

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <assert.h>
    4. #include <time.h>
    5. //打印
    6. void PrintArray(int* a, int n);
    7. // 插入排序
    8. void InsertSort(int* a, int n);
    9. // 希尔排序
    10. void ShellSort(int* a, int n);
    11. // 选择排序
    12. void SelectSort(int* a, int n);
    13. // 堆排序
    14. void AdjustDwon(int* a, int n, int root);
    15. void HeapSort(int* a, int n);
    16. // 冒泡排序
    17. void BubbleSort(int* a, int n);
    18. // 快速排序hoare版本
    19. int PartSort1(int* a, int left, int right);
    20. // 快速排序挖坑法
    21. int PartSort2(int* a, int left, int right);
    22. // 快速排序前后指针法
    23. int PartSort3(int* a, int left, int right);
    24. // 快速排序递归实现
    25. void QuickSort(int* a, int left, int right);
    26. // 快速排序 非递归实现
    27. void QuickSortNonR(int* a, int left, int right);
    28. // 归并排序递归实现
    29. void MergeSort(int* a, int n);
    30. // 归并排序非递归实现
    31. void MergeSortNonR(int* a, int n);
    32. // 计数排序
    33. void CountSort(int* a, int n);

    运行结果:

    1万数据所需要时间

     10万数据所需要时间

    经过简单对比相信大家对不同排序也有一定的理解!

  • 相关阅读:
    软件企业需要每年年审吗?
    Flink之Watermark
    CesiumJS【Basic】- #024A mp4/mov 转 webm
    3. MongoDB高级进阶
    【Go 编程实践】从零到一:创建、测试并发布自己的 Go 库
    JavaScript 基础第四天笔记
    idea Springboot在线商城系统VS开发mysql数据库web结构java编程计算机网页源码maven项目
    JVM:运行时数据区-PC寄存器(程序计数器)
    Spring 的面向切面编程(AOP)的使用场景有哪些?
    一文熟悉 Go 的分支结构(if - else-if - else、switch)
  • 原文地址:https://blog.csdn.net/includeevey/article/details/126610428