当前位置:   article > 正文

C语言数据结构——快速排序和归并排序_c语言数据结构快速排序

c语言数据结构快速排序

一、快速排序

1.1 基本思想

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

1.2 将数组分成两个部分的方法

1.2.1 hoare版本

实现思路:取left为tmp则从right开始,取right为tmp则从left开始,这里我们取left为tmp, right先走,遇到比tmp小的值停下,然后left开始走遇到比tmp大的值停止,然后left和right所指向的数据交换位置,然后继续重复,直到right和left相遇,相遇位置的数据和tmp交换此时以tmp为中心,数组被分成了两个部分然后将两个部分看成新数组,进行递归排序,当数组只有一个值的时候排序结束
  1. //1.quare法
  2. int partsort1(int* a, int begin, int end)
  3. {
  4. int left = begin, right = end;
  5. int tmp = left;
  6. while (left < right)
  7. {
  8. while (left < right && a[right] >= a[tmp])
  9. {
  10. right--;
  11. }
  12. while (left < right && a[left] <= a[tmp])
  13. {
  14. left++;
  15. }
  16. Swap(&a[left], &a[right]);
  17. }
  18. Swap(&a[tmp], &a[right]);
  19. tmp = left;
  20. return tmp;
  21. }

1.2.2 挖坑法

基本思路:挖坑法顾名思义就是一个挖坑填坑的过程,tmp在那边,坑就从那边开始, 如图left为tmp,则坑就在left,然后right开始找小,找到后将其值赋给坑,此时right变新坑,然后left找大,找到后将其值赋给坑,然后left变新坑,当left和right相遇时,将tmp放到坑里,单次循环结束,然后数组以tmp为中心被分成两个部分每个部分看成一个新数组,进行递归排序,当数组只剩下一个值的时候,排序完成。
  1. //2.挖坑法
  2. int partsort2(int* a, int begin, int end)
  3. {
  4. int left = begin, right = end;
  5. int tmp = a[left];
  6. int pit = left;
  7. while (left < right)
  8. {
  9. while (left < right && a[right] >= tmp)
  10. {
  11. right--;
  12. }
  13. a[pit] = a[right];
  14. pit = right;
  15. while (left < right && a[left] <= tmp)
  16. {
  17. left++;
  18. }
  19. a[pit] = a[left];
  20. pit = left;
  21. }
  22. a[pit] = tmp;
  23. return pit;
  24. }

1.2.3 前后指针版本

基本思想: 设立前后指针prev,cur,取左边为tmp,然后cur找小,找到大时cur跳过,当找到小的时候,此时prev+1要么和cur重合,要么prev+1指向的值大于tmp,所以prev+1和cur指向的值交换,当cur越界的时候,prev和tmp交换,此时以tmp为中心,分成两个部分每个部分看成一个新的数组进行递归排序。
  1. //3.双指针法
  2. int partsort3(int* a, int begin, int end)
  3. {
  4. int prev = begin;
  5. int cur = begin + 1;
  6. int tmp = begin;
  7. while (cur<=end)
  8. {
  9. if (a[cur] < a[tmp])
  10. {
  11. prev++;
  12. Swap(&a[prev], &a[cur]);
  13. }
  14. cur++;
  15. }
  16. Swap(&a[tmp], &a[prev]);
  17. tmp = prev;
  18. return tmp;
  19. }

1.3 快排的递归实现及非递归实现

1.3.1 递归实现

基本思路:快排的递归实现主要是着力于 快排基本框架和二叉树的前序遍历类似,所以用利用二叉树的前序遍历递归写出即可。

代码后续会贴出。

1.3.2 非递归实现

基本思路:非递归快排我们用 栈的的后进先出来实现(又是c语言库里没有栈,所以需要自己写,不太了解的老铁可以看我之前的文章,有写如何实现栈), 因为快排每次排序都将数组分成两个部分,所以我们先将最开始的数组的左右下标压入栈中,然后在出栈,对其进行排序,然后把新的到的两个数组各自的左右下标压入栈中,当栈为空的时候,排序完成

1.4 快排的问题及优化

1.4.1 快排的问题

1.tmp是最大或者最小的一些特殊情况下,时间复杂度为0(N^2)
2.当数比较多的时候,递归所需要开辟的栈过多,容易造成栈溢出

1.4.2 快排的优化

1.三数取中法

三数取中法就是,取left,right以及数组中间的值,比较三者的大小,取中间值作为tmp,这样就避免了tmp是最大或者最小值的情况。
  1. //三数取中法优化
  2. int GetMidIndex(int* a, int begin, int end)
  3. {
  4. int min = (begin + end) / 2+1;
  5. if (a[begin] < a[min])
  6. {
  7. if (a[min] < a[end])
  8. {
  9. return min;
  10. }
  11. else if (a[begin] > a[end])
  12. {
  13. return begin;
  14. }
  15. else
  16. {
  17. return end;
  18. }
  19. }
  20. else
  21. {
  22. if (a[begin] < a[end])
  23. {
  24. return begin;
  25. }
  26. else if (a[min] > a[end])
  27. {
  28. return min;
  29. }
  30. else
  31. {
  32. return end;
  33. }
  34. }
  35. }

2.小区间优化法

当数组长度较小时,也就是递归展开的最后几层,所需要的栈占总需求的大部分,此时如果用插入排序就可以有效避免栈溢出的现象。

代码后续贴出。

1.5 快排的总体特性

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳定性:不稳定,如下图所示

1.6 代码

  1. //1.quare法
  2. int partsort1(int* a, int begin, int end)
  3. {
  4. int left = begin, right = end;
  5. int tmp = left;
  6. while (left < right)
  7. {
  8. while (left < right && a[right] >= a[tmp])
  9. {
  10. right--;
  11. }
  12. while (left < right && a[left] <= a[tmp])
  13. {
  14. left++;
  15. }
  16. Swap(&a[left], &a[right]);
  17. }
  18. Swap(&a[tmp], &a[right]);
  19. tmp = left;
  20. return tmp;
  21. }
  22. //2.挖坑法
  23. int partsort2(int* a, int begin, int end)
  24. {
  25. int left = begin, right = end;
  26. int tmp = a[left];
  27. int pit = left;
  28. while (left < right)
  29. {
  30. while (left < right && a[right] >= tmp)
  31. {
  32. right--;
  33. }
  34. a[pit] = a[right];
  35. pit = right;
  36. while (left < right && a[left] <= tmp)
  37. {
  38. left++;
  39. }
  40. a[pit] = a[left];
  41. pit = left;
  42. }
  43. a[pit] = tmp;
  44. return pit;
  45. }
  46. //3.双指针法
  47. int partsort3(int* a, int begin, int end)
  48. {
  49. int prev = begin;
  50. int cur = begin + 1;
  51. int tmp = begin;
  52. while (cur<=end)
  53. {
  54. if (a[cur] < a[tmp])
  55. {
  56. prev++;
  57. Swap(&a[prev], &a[cur]);
  58. }
  59. cur++;
  60. }
  61. Swap(&a[tmp], &a[prev]);
  62. tmp = prev;
  63. return tmp;
  64. }
  65. //三数取中法优化
  66. int GetMidIndex(int* a, int begin, int end)
  67. {
  68. int min = (begin + end) / 2+1;
  69. if (a[begin] < a[min])
  70. {
  71. if (a[min] < a[end])
  72. {
  73. return min;
  74. }
  75. else if (a[begin] > a[end])
  76. {
  77. return begin;
  78. }
  79. else
  80. {
  81. return end;
  82. }
  83. }
  84. else
  85. {
  86. if (a[begin] < a[end])
  87. {
  88. return begin;
  89. }
  90. else if (a[min] > a[end])
  91. {
  92. return min;
  93. }
  94. else
  95. {
  96. return end;
  97. }
  98. }
  99. }
  100. //快排
  101. void QuickSort(int* a, int begin, int end)
  102. {
  103. if (begin >= end)
  104. {
  105. return;
  106. }
  107. int tmp = GetMidIndex(a, begin, end);
  108. //小区间优化
  109. if (end - begin < 15)
  110. {
  111. InsertSort(a + begin, end - begin+1);
  112. }
  113. else
  114. {
  115. tmp = partsort2(a, begin, end);
  116. QuickSort(a, begin, tmp - 1);
  117. QuickSort(a, tmp + 1, end);
  118. }
  119. }
  120. //非递归快排
  121. void QuickSortNonR(int* a, int begin, int end)
  122. {
  123. Stack st;
  124. StackInit(&st);
  125. StackPush(&st, begin);
  126. StackPush(&st, end);
  127. while (StackEmpty(&st))
  128. {
  129. //后进先出,右边后进的所以先取右边
  130. int right = StackTop(&st);
  131. StackPop(&st);
  132. int left = StackTop(&st);
  133. StackPop(&st);
  134. int tmp = partsort1(a, left, right);
  135. //先排左边,就要先入右边
  136. if (tmp + 1 < right)
  137. {
  138. StackPush(&st, tmp + 1);
  139. StackPush(&st, right);
  140. }
  141. if (left < tmp - 1)
  142. {
  143. StackPush(&st, left);
  144. StackPush(&st, tmp-1);
  145. }
  146. }
  147. StackDestroy(&st);
  148. }

二、归并排序

2.1 基本思想

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

2.2 递归实现

基本思路:因为归并排序的展开图和二叉树类似,而且归并排序的核心思想是先让子序段有序,在合并,所以可以用 二叉树的后序遍历的思想来实现
单次排序的思路;单次排序类似于将 两个有序数组合并成一个有序数组的过程,先创建一个长度和原数组长度相等的数组tmp,然后将两个有序的子序数组取小尾插进tmp里,全部插入后,再将tmp的数据memcpy进原数组
  1. //归并排序单次排序
  2. void _MergeSort(int* a, int begin, int end, int* tmp)
  3. {
  4. if (begin >= end)
  5. {
  6. return;
  7. }
  8. int mid = (begin + end) / 2;
  9. //分解
  10. _MergeSort(a, begin, mid, tmp);
  11. _MergeSort(a, mid+1, end, tmp);
  12. //合并
  13. int begin1 = begin,begin2=mid+1;
  14. int i = begin;
  15. while (begin1 <= mid && begin2 <= end)
  16. {
  17. if (a[begin1] <= a[begin2])
  18. {
  19. tmp[i++] = a[begin1++];
  20. }
  21. else
  22. {
  23. tmp[i++] = a[begin2++];
  24. }
  25. }
  26. while (begin1 <= mid)
  27. {
  28. tmp[i++] = a[begin1++];
  29. }
  30. while (begin2 <= end)
  31. {
  32. tmp[i++] = a[begin2++];
  33. }
  34. memcpy(a+begin, tmp+begin, sizeof(int)*(end - begin + 1));
  35. }
  36. //归并排序递归
  37. void MergeSort(int* a, int n)
  38. {
  39. int* tmp = (int*)malloc(sizeof(int) * n);
  40. if (tmp == NULL)
  41. {
  42. perror("malloc");
  43. exit(-1);
  44. }
  45. _MergeSort(a, 0, n - 1, tmp);
  46. free(tmp);
  47. tmp = NULL;
  48. }

2.3 非递归实现

基本思路:设置一个参数rangeN来模拟实现归并排序,当rangeN=1时模拟的是递归的最后一层的实现,为2则是倒数第二层的实现,每次rangeN*2,当rangeN大于数组长度时,排序完成。
  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");
  8. exit(-1);
  9. }
  10. int rangeN = 1;
  11. while (rangeN < n)
  12. {
  13. for (int i = 0; i < n; i += rangeN * 2)
  14. {
  15. int begin1 = i, end1 = i + rangeN - 1;
  16. int begin2 = end1 + 1, end2 = begin2 + rangeN - 1;
  17. int j = i;
  18. //判断越界情况
  19. //end1,begin2,end2都越界
  20. if (end1 >= n)
  21. {
  22. break;
  23. }
  24. //begin2,end2越界
  25. else if (begin2 >= n)
  26. {
  27. break;
  28. }
  29. //end2越界
  30. else if (end2 >= n)
  31. {
  32. end2 = n - 1;
  33. }
  34. while (begin1 <= end1 && begin2 <= end2)
  35. {
  36. if (a[begin1] <= a[begin2])
  37. {
  38. tmp[j++] = a[begin1++];
  39. }
  40. else
  41. {
  42. tmp[j++] = a[begin2++];
  43. }
  44. }
  45. while (begin1 <= end1)
  46. {
  47. tmp[j++] = a[begin1++];
  48. }
  49. while (begin2 <= end2)
  50. {
  51. tmp[j++] = a[begin2++];
  52. }
  53. memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
  54. }
  55. rangeN *= 2;
  56. }
  57. free(tmp);
  58. tmp = NULL;
  59. }

注意:写代码时需要注意一下数组越界的情况。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/880649
推荐阅读
相关标签
  

闽ICP备14008679号