• 【C语言】快速排序__拓展


            其实就是对快速排序的优化。


    一、挖坑法

            这种方法其主要是好理解,其快排的本质却没有改变。

            其主要思路是,把key对应位置空出来,然后两边同样找大找小,只不过这次把找到的数,放到空位置处

            找到最后左边和右边相遇,此时相遇的位置必定为坑,再把key放过来就好。

            这样一次排序就完成了。如果其左边有坑,天然要从右边开始找。如果key找的是右边,其天然要从左边开始找。

            这种找法跟传统的排序其本质差不多,但是要注意其数据移动的位置的不同于传统快排的。如果有选择题是,快排的移动顺序的题目,要考虑到这个点。

    1. //挖坑法
    2. int PostSort(int* arr, int begin, int end)
    3. {
    4. int key = arr[begin];
    5. int piti = begin;
    6. while (begin<end)
    7. {
    8. while (begin<end && arr[end]>=key) {
    9. end--;
    10. }
    11. arr[piti] = arr[end];
    12. piti = end;
    13. while (begin<end && arr[begin]<=key)
    14. {
    15. begin++;
    16. }
    17. arr[piti] = arr[begin];
    18. piti = begin;
    19. }
    20. arr[piti] = key;
    21. return piti;
    22. }
    23. void QuickSort(int* arr, int begin, int end)
    24. {
    25. if (begin >= end) {
    26. return;
    27. }
    28. int pos = PostSort(arr,begin,end);
    29. QuickSort(arr, begin, pos - 1);
    30. QuickSort(arr, pos + 1, end);
    31. }

    二、前后指针法

            定义两个指针,cur往前走,遇到数据小于 key 则 pre 往前走一步。cur遇到比key大的,自己往前走,pre不动。

            直到遇到下一个比key小的数,pre往前走一步,然后交换数据。

             

            

            cur遇到比key大的数据后,两个指针就拉开了距离。

     

             继续重复上面的步骤,直到cur到头后,交换key和pre位置的数据。

     

    代码:

    1. //前后指针法
    2. int PostSort2(int* arr, int begin, int end)
    3. {
    4. int cur = begin + 1;
    5. int pre = begin;
    6. int key = arr[begin];
    7. while(cur <= end) {
    8. if (arr[cur]<key && ++pre!=cur) {
    9. swap(&arr[pre], &arr[cur]);
    10. }
    11. cur++;
    12. }
    13. swap(&arr[pre],&arr[begin]);
    14. return pre;
    15. }
    16. void QuickSort(int* arr, int begin, int end)
    17. {
    18. if (begin >= end) {
    19. return;
    20. }
    21. int pos = PostSort2(arr, begin, end);
    22. QuickSort(arr, begin, pos - 1);
    23. QuickSort(arr, pos + 1, end);
    24. }

    三、 快排速度优化

            注意,理解了快速排序的原理,那么就知道,如果key找到后的位置,一直是中位数,那么其数组区间二分 logN。递归下去每一层的时间复杂度为 N。

            所以得,其快速排序时间复杂度最快的情况为 N*logN。

            如果key选择的是最小或者最大,那么其为最坏的情况。而如果数组有序(或者接近有序),那么其选择的key一定为最小或最大。

            其时间复杂度为一个等差数列,所以得出,快速排序在有序或者接近有序的情况下,时间复杂度为O(N^2)。

    PS:而且这里递归太深会造成,栈溢出。

    1、三数取中

            取开始 中间 结尾,选不是最大,也不是最小那个。强行把数组变为二分,提高运行效率。

    1. //三数取中
    2. int GetMidIndex(int* arr, int begin, int end)
    3. {
    4. int mid = (begin + end) / 2;
    5. if (arr[begin] < arr[mid]) {
    6. if (arr[mid] < arr[end])
    7. {
    8. return mid;
    9. }
    10. else if (arr[end] < arr[begin])
    11. {
    12. return begin;
    13. }
    14. else //arr[begin] < arr[end]
    15. {
    16. return end;
    17. }
    18. }
    19. else { // arr[begin] > arr[mid]
    20. if (arr[mid] > arr[end])
    21. {
    22. return mid;
    23. }
    24. else if (arr[begin] > arr[end])
    25. {
    26. return end;
    27. }
    28. else //arr[begin]<arr[end]
    29. {
    30. return begin;
    31. }
    32. }
    33. }
    34. //前后指针法
    35. int PostSort2(int* arr, int begin, int end)
    36. {
    37. int cur = begin + 1;
    38. int pre = begin;
    39. int key = begin;
    40. int mid = GetMidIndex(arr, begin, end);
    41. swap(&arr[key],&arr[mid]);
    42. while(cur <= end) {
    43. if (arr[cur]<arr[key] && ++pre != cur) {
    44. swap(&arr[pre], &arr[cur]);
    45. }
    46. cur++;
    47. }
    48. swap(&arr[pre],&arr[begin]);
    49. return pre;
    50. }
    51. void QuickSort(int* arr, int begin, int end)
    52. {
    53. if (begin >= end) {
    54. return;
    55. }
    56. int pos = PostSort2(arr, begin, end);
    57. QuickSort(arr, begin, pos - 1);
    58. QuickSort(arr, pos + 1, end);
    59. }

    2、小区间优化

            假设一个数组进行快速排序,其递归图入下。

             这里可以发现,部分小区间,其实就没必要再使用快速排序,这里可以使用其他排序来代替。

    PS:这里把最后一层优化掉,就相当于去掉了50%的调用。

    1. //前后指针法
    2. int PostSort2(int* arr, int begin, int end)
    3. {
    4. int cur = begin + 1;
    5. int pre = begin;
    6. int key = begin;
    7. int mid = GetMidIndex(arr, begin, end);
    8. swap(&arr[key],&arr[mid]);
    9. while(cur <= end) {
    10. if (arr[cur]<arr[key] && ++pre != cur) {
    11. swap(&arr[pre], &arr[cur]);
    12. }
    13. cur++;
    14. }
    15. swap(&arr[pre],&arr[begin]);
    16. return pre;
    17. }
    18. // 插入排序
    19. void InsertSort(int* a, int n)
    20. {
    21. for (int i = 0; i < n; i++) {
    22. int end = i;
    23. int temp = a[end + 1];
    24. while (end >= 0)
    25. {
    26. if (temp < a[end]) {
    27. a[end + 1] = a[end];
    28. end--;
    29. }
    30. else {
    31. break;
    32. }
    33. a[end + 1] = temp;
    34. }
    35. }
    36. }
    37. //小区间优化
    38. void QuickSort_2(int* arr, int begin, int end)
    39. {
    40. if (begin >= end) {
    41. return;
    42. }
    43. if (end - begin > 2) { //区间小于 2 ,就使用插入排序
    44. int pos = PostSort2(arr, begin, end);
    45. QuickSort(arr, begin, pos - 1);
    46. QuickSort(arr, pos + 1, end);
    47. }
    48. else {
    49. //插入排序
    50. InsertSort(arr + begin, end - begin + 1 ); //
    51. }
    52. }

    3、递归改非递归

            如果递归深处太深,那么会出现栈溢出。那么这里可以使用数据结构——栈来模拟递归的结构。因为这里栈的数据都在堆上生成的,堆的空间很大,所以可以进行深层次的调用。

     

            首先为了对应其递归的顺序,这里先把right入栈,这样出来先使用的就是left。

            再这个区间内排序完成后呢,再次把以key为分割的两个区间,再入栈。

             然后再进行排序,不过这里要注意,这里key-1到 left 的区间要全部排完后,才会轮到key+1到 right 区间排序

            会发现这里就跟递归非常相似了(跟二叉树的前序遍历也非常像)。

    代码:

    1. //前后指针法
    2. int PostSort2(int* arr, int begin, int end)
    3. {
    4. int cur = begin + 1;
    5. int pre = begin;
    6. int key = begin;
    7. int mid = GetMidIndex(arr, begin, end);
    8. swap(&arr[key],&arr[mid]);
    9. while(cur <= end) {
    10. if (arr[cur]<arr[key] && ++pre != cur) {
    11. swap(&arr[pre], &arr[cur]);
    12. }
    13. cur++;
    14. }
    15. swap(&arr[pre],&arr[begin]);
    16. return pre;
    17. }
    18. //快速排序 递归改非递归
    19. void QuickSortNonR(int* arr, int begin, int end)
    20. {
    21. stack St;
    22. StackInit(&St);
    23. StackPush(&St,end);
    24. StackPush(&St,begin);
    25. while (!StackIsEmpty(&St))
    26. {
    27. int left = StackTop(&St);
    28. StackPop(&St);
    29. int right = StackTop(&St);
    30. StackPop(&St);
    31. int key = PostSort2(arr,left,right);
    32. if (right > key + 1) {
    33. StackPush(&St, right);
    34. StackPush(&St, key + 1);
    35. }
    36. if (left < key - 1) {
    37. StackPush(&St, key-1);
    38. StackPush(&St, left);
    39. }
    40. }
    41. StackDestory(&St);
    42. }

    测试:

     

    拓展思路:

            使用队列来模拟递归,实现快速排序。其与用栈模拟的方式,单趟排序的顺序不一样

            这里先把 left 和 right 入列,在此区间内进行排序。

            排序完成后把以key分割的两个区间入列。

            这里前一个区间排序完成后,把它下一个区间入列。

             再下一次取出的区间是6 到 9 ,会发现跟前面的二叉树的层序遍历相似。

             虽然其排序的顺序跟递归的方式不同,但是其每个区间相互不影响,所以其结果是相同的,效果一样。


  • 相关阅读:
    Docker基础
    基于CentOS7.5构建LVS-DR 群集,并启用Nginx负载均衡,一键完成。
    苦瓜功能稻降碳90% 国稻种芯-517:何登骥水稻旱作节水80%
    C#上位机开发目录
    第四十五章 命名空间和数据库 - 数据库基础知识
    idea使用gradle教程 (idea gradle springboot)2024
    数据库容灾GoldenGate
    设计模式详解(十二)——外观模式
    语音识别与自然语言处理(NLP):技术前沿与未来趋势
    请勿使用PostMessage来模拟键盘输入
  • 原文地址:https://blog.csdn.net/weixin_45423515/article/details/125508565