• 快速排序【C语言数据结构】


    目录

    一、快速排序的基础实现

    1.经典写法

    2.挖坑法

    3.前后指针法

    二、快速排序第二部分的写法

    三、优化我们基准值的选取

    四、快速排序的非递归写法

    五、代码的汇总

    六、测试代码


    快速排序,顾名思义,在几大排序算法中所持的时间复杂度是比较低的,其核心思路就是在一串无序的数组中选择一个数字作为基准,然后分别从这个数组的首尾两端进行比较,在这两个首尾指针不逆位的前提下分别找到从后往前的第一个小于基准的数,和从前往后第一个大于基准的数,然后将这两个数交换顺序,以此类推。最终两个指针相交的位置就是我们基准数字应该存在的位置。此时所有比基准数字小的全部都排在基准数字之前(无序),所有比基准数字大的全部都排在基准数字之后(无序)。然后我们分别以递归的方式将我们基准数字之前部分的数组和基准数字之后的数组进行进一步的排序。以递归的形式最终将整个数组排序完成。

    屏幕录制2022-06-26 08.27.27

    一、快速排序的基础实现

    我们的快速排序以两部分代码实现,一部分以寻找基准数,然后左右指针从前往后,从后往前比较,并且返回最终基准数的位置的代码。

    另一部分为递归调用我们的第一部分的代码,从而实现我们整一个数组的有序。

    1.经典写法

    1. int PartSort1(ElemType* a, int begin, int end)
    2. {
    3. //用我们的left去接收我们的begin函数
    4. int left=begin;
    5. //用我们的right去接收我们的end函数
    6. int right=end;
    7. //将我们的left作为我们的基准数
    8. int key=left;
    9. //当我们的左指针的位置小于我们右指针的位置的时候进入循环
    10. while(left<right)
    11. {
    12. //第一个while循环从右往左找到我们第一个小于基准数的位置
    13. while(left<right&&a[right]>=a[key])
    14. {
    15. right--;
    16. }
    17. //第二个while循环从左往右找到我们第一个大于基准数的位置
    18. while(left<right&&a[left]<=a[key])
    19. {
    20. left++;
    21. }
    22. //交换我们的左右指针所指向的数据
    23. Swap(&a[left],&a[right]);
    24. }
    25. Swap(&a[key],&a[left]);
    26. //将我们的key也就是我们的基准数指向我们的left的位置
    27. //因为排序结束后我们left左边的数全部都小于基准,left右边的数全部都大于基准,我们的left位置的数已经找到了它的位置。
    28. key=left;
    29. return key;
    30. }

    2.挖坑法

    挖坑法相比较于经典写法并没有实质性的区别,但是相对于经典版的写法更加容易理解。

    挖坑法的主要思想就是将我们的基准数先用一个临时变量来保存,从而我们的基准数的位置形成了一个坑,然后之后从右往前找第一个小于基准数,从左往右找第一个大于基准数的时候,就直接将我们的数填入我们的坑中就可以。

    1. int PartSort2(ElemType* a, int begin, int end)
    2. {
    3. //用我们的left函数去接收我们的begin
    4. int left=begin;
    5. //用我们的right函数去接收我们的end函数
    6. int right=end;
    7. //将我们的基准数的具体值存储到我们的key中
    8. int key=a[begin];
    9. //标记我们的坑的位置为我们的begin
    10. int hole=begin;
    11. //当我们的左指针小于我们的右指针的时候
    12. while(left<right)
    13. {
    14. //找到我们从右往左的第一个小于基准数的位置
    15. while(left<right&&a[right]>=key)
    16. {
    17. right--;
    18. }
    19. //将我们找到的小于基准数的数与我们的坑的位置交换,也就是将我们小于基准数的数放入我们的坑中
    20. a[hole]=a[right];
    21. //由于我们将我们小于基准数的数移动到了我们的坑的位置,那么此时我们小于基准数的原位置就是我们新的坑位。
    22. hole=right;
    23. //找到我们从左往右的第一个大于我们基准数的数
    24. while(left<right&&a[left]<=key)
    25. {
    26. left++;
    27. }
    28. //以此类推,将我们第一个找到的大于我们的基准数放入我们的坑中
    29. a[hole]=a[left];
    30. hole=left;
    31. }
    32. //最终在经过我们的所有的循环之后,我们最终空出来的坑位就是我们的基准数的应该放入的位置
    33. a[hole]=key;
    34. //将我们的基准数的位置返回。
    35. return hole;
    36. }

    3.前后指针法

    前后之阵发的算法就是设置一个前序指针和一个滞后指针。前序指针不断向前遍历数组,当找到小于基准值的数据的时候,就将我们的前置指针的数据与我们滞后指针+1的位置进行交换。这样就将我们所有小于基准值的数据全部都交换到了数组的前端,所有大于基准值的数据移动到了我们的后端。最终我们将我们的基准位置与我们的滞后指针所指向的位置进行交换,并且返回我们的基准值指向的位置。

    1. int PartSort3(ElemType* a, int begin, int end)
    2. {
    3. //设置一个滞后指针
    4. int prev=begin;
    5. //设置一个前置指针
    6. int cur=begin+1;
    7. //我们的基准数设置为我们的begin位置的数据
    8. int key=begin;
    9. //当我们的前序指针小于我们的end位置的时候进行循环遍历
    10. while(cur<=end)
    11. {
    12. //如果我们的前序指针小于我们的基准数并且我们的前置指针不为我们的滞后指针的下一个位置的时候
    13. if(a[cur]<a[key]&&++prev!=cur)
    14. {
    15. //将我们前后指针的数据内容进行交换
    16. Swap(&a[cur],&a[prev]);
    17. }
    18. //将我们的前置指针往后移动一个位置
    19. cur++;
    20. }
    21. //将我们基准数据与我们的滞后指针所指向的数据进行交换,并且将我们的基准位置返回。
    22. Swap(&a[prev],&a[key]);
    23. key=prev;
    24. return key;
    25. }

    二、快速排序第二部分的写法

    快速排序第二部分的写法就是先找到我们的基准值,然后分别对我们begin到基准值前一个数据进行递归排序,再对我们begin+1到我么数组尾的数据进行递归排序。

    这里我们可以对我们的快速排序进行一个优化,由于我们知道我们快速排序类似于我们的二叉树,在末尾的最后几层虽然只要一两个数,但是数据量往往是整个快速排序的一半,所以当我们的所要排序的数据个数小于一定个数的时候,我们可以采用插入排序或者其他的排序来快速地实现

    1. void QuickSort(ElemType *a,int begin,int end)
    2. {
    3. //如果我们的begin>end就代表着我们这一小部分的数据已经排序完成,就直接return
    4. if(begin>end)
    5. {
    6. return;
    7. }
    8. //如果我们的end-begin的数据大于10个
    9. //我们用我们之前的第一部分的三种写法中的一种来找到我们的第一个基准值然后递归调用分别将
    10. //基准值前面的数组和基准值后面的数组进行排序。
    11. if(end-begin>10)
    12. {
    13. int key= PartSort3(a,begin,end);
    14. QuickSort(a,begin,key-1);
    15. QuickSort(a,key+1,end);
    16. }
    17. else
    18. {
    19. //当我们的待排序的部分的长度小于等于10的时候,我们就直接用我们的插入排序来实现。
    20. InsertSort1(a+begin,end-begin+1);
    21. }
    22. }

    三、优化我们基准值的选取

    我们在上面的代码中全部都是选取我们的left作为我们的基准值,但如果想让我们的快速排序实现最佳的性能,我们需要找到每一段数据的中间值为我们的基准值。

    当然我们可以采用随机数的写法,但更加优的写法是将我们的三分取中法,就是分别找到我们的首元素,我们的尾元素,然后我们的首+尾/2的中间元素,通过下面的比较我们的可以在这三者之间找到我们的中间元素。

    1. int GetMidIndex(int* a, int begin, int end)
    2. {
    3. int mid=(begin+end)/2;
    4. if(a[begin]>a[mid])
    5. {
    6. if(a[mid]>a[end])
    7. {
    8. return mid;
    9. }
    10. else if(a[end]<a[begin])
    11. {
    12. return end;
    13. }
    14. else
    15. {
    16. return begin;
    17. }
    18. }
    19. else
    20. {
    21. if(a[end]>a[mid])
    22. {
    23. return mid;
    24. }
    25. else if(a[end]<a[begin])
    26. {
    27. return begin;
    28. }
    29. else
    30. {
    31. return end;
    32. }
    33. }
    34. }

    四、快速排序的非递归写法

    快速排序的非递归写法我们需要借助于栈或者队列的数据结构来辅助实现。需要栈和队列的代码可以从下面两篇博文中自取

    其核心思想就是将我们的尾指针压入栈,首指针压入栈,然后从我们的栈中取出我们的首尾指针,调用我们的上面第一部分的排序算法来找到我们的基准值,然后再分别将我们begin到基准值-1的首尾区间压入栈中,再将我们基准值+1到我们的end的位置压入我们的栈中,然后循环来实现我们的快速排序

    当然,如果要用我们的队列来实现的话,就能以类似层序遍历的方式来实现

    以下为我们以栈辅助来实现非递归写法的方式。

    栈的实现(c语言数据结构)_桜キャンドル淵的博客-CSDN博客

    队列的实现(c语言数据结构)_桜キャンドル淵的博客-CSDN博客

    1. void QuickSortNonR(ElemType*a,int begin,int end)
    2. {
    3. //定义一个栈
    4. ST st;
    5. //初始化我们的栈
    6. StackInit(&st);
    7. //将我们首尾元素压入栈中
    8. StackPush(&st,end) ;
    9. StackPush(&st,begin);
    10. //当我们当前的栈不为空的时候进入循环
    11. while(!StackEmpty(&st))
    12. {
    13. //取出我们栈顶的区间
    14. int left= StackTop(&st);
    15. StackPop(&st);
    16. int right= StackTop(&st);
    17. StackPop(&st);
    18. //用我们的第一部分的代码来找到我们的基准值
    19. int key= PartSort3(a,left,right);
    20. //如果我们的排序的区间大于1,我们就继续将我们的区间入栈
    21. if(key+1<right)
    22. {
    23. StackPush(&st,right);
    24. StackPush(&st,key+1);
    25. }
    26. if(left<key-1)
    27. {
    28. StackPush(&st,key-1);
    29. StackPush(&st,left);
    30. }
    31. }
    32. //摧毁我们的栈
    33. StackDestory(&st);
    34. }

    五、代码的汇总

    1. int PartSort1(ElemType* a, int begin, int end)
    2. {
    3. int left=begin;
    4. int right=end;
    5. int key=left;
    6. while(left<right)
    7. {
    8. while(left<right&&a[right]>=a[key])
    9. {
    10. right--;
    11. }
    12. while(left<right&&a[left]<=a[key])
    13. {
    14. left++;
    15. }
    16. Swap(&a[left],&a[right]);
    17. }
    18. Swap(&a[key],&a[left]);
    19. key=left;
    20. return key;
    21. }
    22. int PartSort2(ElemType* a, int begin, int end)
    23. {
    24. int left=begin;
    25. int right=end;
    26. int key=a[begin];
    27. int hole=begin;
    28. while(left<right)
    29. {
    30. while(left<right&&a[right]>=key)
    31. {
    32. right--;
    33. }
    34. a[hole]=a[right];
    35. hole=right;
    36. while(left<right&&a[left]<=key)
    37. {
    38. left++;
    39. }
    40. a[hole]=a[left];
    41. hole=left;
    42. }
    43. a[hole]=key;
    44. return hole;
    45. }
    46. int PartSort3(ElemType* a, int begin, int end)
    47. {
    48. //设置一个滞后指针
    49. int prev=begin;
    50. //设置一个前序指针
    51. int cur=begin+1;
    52. //指定我们的比较的数据
    53. int key=begin;
    54. int mid= GetMidIndex(a,begin,end);
    55. while(cur<=end)
    56. {
    57. if(a[cur]<a[key]&&++prev!=cur)
    58. {
    59. Swap(&a[cur],&a[prev]);
    60. }
    61. cur++;
    62. }
    63. Swap(&a[prev],&a[key]);
    64. key=prev;
    65. return key;
    66. }
    67. void QuickSort(ElemType *a,int begin,int end)
    68. {
    69. if(begin>end)
    70. {
    71. return;
    72. }
    73. if(end-begin>10)
    74. {
    75. int key= PartSort3(a,begin,end);
    76. QuickSort(a,begin,key-1);
    77. QuickSort(a,key+1,end);
    78. }
    79. else
    80. {
    81. InsertSort1(a+begin,end-begin+1);
    82. }
    83. }
    84. void QuickSortNonR(ElemType*a,int begin,int end)
    85. {
    86. //定义一个栈
    87. ST st;
    88. StackInit(&st);
    89. StackPush(&st,end) ;
    90. StackPush(&st,begin);
    91. while(!StackEmpty(&st))
    92. {
    93. int left= StackTop(&st);
    94. StackPop(&st);
    95. int right= StackTop(&st);
    96. StackPop(&st);
    97. int key= PartSort3(a,left,right);
    98. if(key+1<right)
    99. {
    100. StackPush(&st,right);
    101. StackPush(&st,key+1);
    102. }
    103. if(left<key-1)
    104. {
    105. StackPush(&st,key-1);
    106. StackPush(&st,left);
    107. }
    108. }
    109. StackDestory(&st);
    110. }

    六、测试代码

    1. #include "sort.h"
    2. int main() {
    3. int b[400];
    4. for (int i =0;i<400;i++)
    5. {
    6. b[i]=rand() % 100;
    7. }
    8. print(b,400);
    9. QuickSort(b,0,399);
    10. QuickSortNonR(b,0,399);
    11. print(b,400);
    12. }

  • 相关阅读:
    上网管理系统--帮助企业管理和分析员工上网行为
    全局异常处理类BaseExceptionHandler
    [羊城杯2020]easyphp .htaccess的利用
    水利行业评中级职称业绩要求有哪些?帮你分析
    java 自带命令
    【C++】类与对象 III 【 深入浅出理解 类与对象 】
    AI诈骗的防范与应对:维护数字安全的责任
    一文读懂最新的A股交易手续费,建议收藏
    .NET周刊【2月第1期 2024-02-04】
    2150. 找出数组中的所有孤独数字-快速排序
  • 原文地址:https://blog.csdn.net/weixin_62684026/article/details/125466499