当前位置:   article > 正文

快速排序的三种实现方式_快速排序实现

快速排序实现

目录

1.Hoare版本

第一步:

第二步:

第三步:

Hoare图解:

Hoare代码: 

2.挖坑法:

第一步:

第二步:        

第三步:

挖坑法图解:

挖坑法代码: 

3.前后指针版本

第一步:

第二步:

第三步:

前后指针版本图解:

前后指针法代码:

优化:

时间复杂度:

最好情况:

最坏情况: 

总结:


如果给我们一个数组,要求是让我们对这个数组进行排序,但是同时又要求时间复杂度尽可能的小。这个时候冒泡排序(时间复杂度 O(n^2))显然是指望不上了,而这里有一个公认的快速排序的方法,最初是由Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

1.Hoare版本

首先说一下大概思想:

第一步:

首先找一个数作为key值(通常为数组最左边的数,这里其实选择哪个是无所谓的,微调一下后面的代码就行),作用就是在一趟排序后将所在的数组分为两部分:数组的左边是小于key的值,数组的右边是大于key的值,界限就是key。(实际代码中其实是保存key这个数的下标,方便最后的交换)

第二步:

在一趟排序中从数组的右边开始逐一向左查找,找比key小的数,然后再从数组的左边开始从逐一向右查找,找比key大的数,完成上述操作后交换找到的两个数。然后再判断整个数组是否被遍历完,即最后交换的两个数的下标是否相同或错位。没有遍历完的话继续重复上述交换操作,直到遍历完结束。最后将key与最后遍历结束的位置的数交换位置。

此时的这一趟操作就会完成对数组关于key值左右大小的分割。

第三步:

对上趟排序后key值左右的两个数组再次进行找key值并进行划分,也就是所谓的函数递归了。直到递归到不能再割分为止。这时的数组就被排好序了。

Hoare图解:

最后会发现全都变为区间为一的数组了,此时的整个数组也有序了。

Hoare代码: 

(递归版本)

  1. void Swap(int* pa, int* pb)
  2. {
  3. int tmp = *pa;
  4. *pa = *pb;
  5. *pb = tmp;
  6. }
  7. int PartSort1(int* a, int left, int right) //函数返回key的下标
  8. {
  9. int keyi = left;
  10. while (left < right)
  11. {
  12. while (left < right && a[right] >= a[keyi])
  13. {
  14. right--;
  15. }
  16. while (left < right && a[left] <= a[keyi])
  17. {
  18. left++;
  19. }
  20. Swap(&a[left], &a[right]);
  21. }
  22. Swap(&a[left], &a[keyi]);
  23. return left;
  24. }
  25. void QuickSort(int* a, int left, int right)
  26. {
  27. if (left >= right)
  28. {
  29. return;
  30. }
  31. else
  32. {
  33. int keyi = PartSort1(a, left, right);
  34. QuickSort(a, left, keyi - 1);
  35. QuickSort(a, keyi + 1, right);
  36. }
  37. }

2.挖坑法:

第一步:

首先找一个坑位,把坑位里的值保存到key里,这时的坑位里的值可以被覆盖。

第二步:        

从数组的右边开始向左逐一遍历,找比key小的数,然后把这个值放在坑位里面,把坑位更新成刚刚找到的数的位置。接着从左开始向右逐一遍历,找比key大的数,然后把这个数放在坑位里面,把坑位更新成刚刚找到的数的位置。重复上述操作,直到把数组遍历一遍。最后把key放在最后的坑位里面。

第三步:

key为界限,把数组分为左右两个数组,接着重复第一步和第二步,直到无法再拆分为止。

挖坑法图解:

挖坑法代码: 

(递归版本)

  1. int PartSort2(int* a, int left, int right)
  2. {
  3. int key = a[left];
  4. int pit = left;
  5. while (left < right)
  6. {
  7. while (left < right && a[right] >= key)
  8. {
  9. --right;
  10. }
  11. a[pit] = a[right];
  12. pit = right;
  13. while (left < right && a[left] <= key)
  14. {
  15. ++left;
  16. }
  17. a[pit] = a[left];
  18. pit = left;
  19. }
  20. a[left] = key;
  21. return left;
  22. }
  23. void QuickSort(int* a, int left, int right)
  24. {
  25. if (left >= right)
  26. {
  27. return;
  28. }
  29. else
  30. {
  31. int keyi = PartSort2(a, left, right);
  32. QuickSort(a, left, keyi - 1); //左数组
  33. QuickSort(a, keyi + 1, right); //右数组
  34. }
  35. }

3.前后指针版本

第一步:

初始时,prev指针指向序列开头,cur指针指向prev的下一个位置。keyi指针指向序列开头。

第二步:

cur指针找比key小的值,找到的话就与prev下一个位置的数交换位置。找的过程中如果是找的数比key大,cur指针往后继续查找,prev保持不动。cur完成一遍遍历后,把keyprev指针指向的数交换。

第三步:

key值为界限,把数组分为左右两个数组,然后重复第一步和第二步。

前后指针版本图解:

 

前后指针法代码:

  1. void Swap(int* pa, int* pb)
  2. {
  3. int tmp = *pa;
  4. *pa = *pb;
  5. *pb = tmp;
  6. }
  7. int GetMidIndex(int* a, int left, int right)
  8. {
  9. int midi = (left + right) / 2;
  10. if (a[left] < a[midi])
  11. {
  12. if (a[midi] < a[right])
  13. return midi;
  14. else if (a[left] > a[right])
  15. return left;
  16. else
  17. return right;
  18. }
  19. else
  20. {
  21. if (a[left] < a[right])
  22. return left;
  23. else if (a[midi] > a[right])
  24. return midi;
  25. else
  26. return right;
  27. }
  28. }
  29. int PartSort3(int* a, int left, int right)
  30. {
  31. //优化,下面会讲
  32. int midi = GetMidIndex(a, left, right);
  33. Swap(&a[left], &a[midi]);
  34. int keyi = left;
  35. int prev = left;
  36. int cur = prev + 1;
  37. while (cur <= right)
  38. {
  39. if (a[cur] < a[keyi] && a[cur] != a[++prev])
  40. {
  41. Swap(&a[cur], &a[prev]);
  42. }
  43. cur++;
  44. }
  45. Swap(&a[prev], &a[keyi]);
  46. return prev;
  47. }
  48. void QuickSort(int* a, int left, int right)
  49. {
  50. if (left >= right)
  51. {
  52. return;
  53. }
  54. else
  55. {
  56. int keyi = PartSort3(a, left, right);
  57. QuickSort(a, left, keyi - 1);
  58. QuickSort(a, keyi + 1, right);
  59. }
  60. }

优化:

我们先来看看怎么计算这种算法的时间复杂度

时间复杂度:

最好情况:

每次的key值都是数组的中值!

最坏情况: 

每次的key值都是最大或最小值!

那么我们要尽量避免最坏的情况出现。

这里可以控制key值得选取,假如我们在排序之前随机选取三个数,取三个数中得中值作为key值,就可以一定程度上避免最坏情况的发生!再结合之前写的代码,把找到的中值与最左边的数交换一下,就能在不改变之前代码的基础上完成优化了!

  1. void Swap(int* pa, int* pb)
  2. {
  3. int tmp = *pa;
  4. *pa = *pb;
  5. *pb = tmp;
  6. }
  7. //取三个数中中间位置的数
  8. int GetMidIndex(int* a, int left, int right)
  9. {
  10. int midi = (left + right) / 2;
  11. if (a[left] < a[midi])
  12. {
  13. if (a[midi] < a[right])
  14. return midi;
  15. else if (a[left] > a[right])
  16. return left;
  17. else
  18. return right;
  19. }
  20. else
  21. {
  22. if (a[left] < a[right])
  23. return left;
  24. else if (a[midi] > a[right])
  25. return midi;
  26. else
  27. return right;
  28. }
  29. }

总结:

以上就是快排的三种实现方式啦,其实总体来讲本质都是一样,小的数移到数组左边,大的数移到数组右边。代码这里用的是递归的方式,来完成对数组的一次次排序。

有问题的读者老爷可以在评论区提问哦,同时也欢迎大家多多指正。

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

闽ICP备14008679号