• 快速排序(排序中篇)


    1.快速排序的概念及实现

    2.快速排序的时间复杂度

    3.优化快速排序

    4.关于快速排序的细节

    5.总代码

    1.快速排序的概念及实现

    1.1快速排序的概念

    快速排序的单趟是选一个基准值,然后遍历数组的内容把比基准值大的放右边,比基准值小的放在左边,下面的动图可以简单了解是这么排序的。

    选第一个6作为基准,然后让俩个变量作为下标值去遍历数组,L要选出一个比key(6)要大的值然后停下,R要找到一个比key要小的值然后停下,最后把L和R停下的位置对应的值进行交换,交换完后有继续走,直到L与R相遇,最后把碰面的地方对应的值与key交换,完成一次单趟的快排 ,这样key的左边都是比key小的值,而右边都是比key大的值,与之前比较变得相对有序。

    走完单趟后,后面就会以key的左右边重读第一次操作,就是把数组以key为分界线分为俩个数组(逻辑上),再选出一个基准值,使得基准值的俩边要么比key大要么比key小,这样就会想到递归,一直分到后面就会出现只有一个值的数组,或者不存在的(后面会提到),递归完后的数组就是排好序的了。

    1.2快速排序的实现

    代码:

    1. #include
    2. void swap(int* p1, int* p2)
    3. {
    4. int tmp = *p1;
    5. *p1 = *p2;
    6. *p2 = tmp;
    7. }
    8. void QuickSort(int* a,int left, int right)
    9. {
    10. if (left >= right)
    11. return;
    12. int begin = left;
    13. int end =right;
    14. int keyi = left;
    15. while (begin< end)
    16. {
    17. while (begin < end && a[keyi] <= a[end])
    18. {
    19. end--;
    20. }
    21. while (begin < end && a[keyi] >= a[begin])
    22. {
    23. begin++;
    24. }
    25. swap(&a[begin], &a[end]);
    26. }
    27. swap(&a[keyi], &a[begin]);
    28. keyi = begin;
    29. QuickSort(a, left, keyi - 1);
    30. QuickSort(a, keyi + 1, right);
    31. }
    32. int main()
    33. {
    34. int arr[] = { 11,7,5,9,6,1,4,3,8,8,8,101 };
    35. int size = sizeof(arr) / sizeof(arr[0]);
    36. QuickSort(arr,0,size-1);
    37. for (int i = 0; i < size; i++)
    38. {
    39. printf("%d ", arr[i]);
    40. }
    41. return 0;
    42. }

    代码分析:

    创建begin和end来作为L和R去遍历数组,while循环执行的条件是begin

    下图是不存在的情况:

    2.快速排序的时间复杂度

    本文只是简单计算快速排序的时间复杂度。

     

    3.优化快速排序 

    3.1三数取中

    快速排序遇到有序的数组就会出现问题,时间复杂度会变为O(N^2).

     基准值到最后还是原来的位置,则递归时,每次只会去掉第一个元素,不会像对半分那种,这样每层大约n个,层数也为n个,时间复杂度就为O(N^2),速度会慢很多,所以基准值不应该每次取第一个,可以用随机函数来获取一个拟随机数作为key值,但是也是有可能是最小或者最大(在排升序或者降序会慢),所以用三数取中的方法来定key,但是得到key值还是要和第一个值交换一下,保证上面的代码逻辑不变,假如下标为3的值适合作key就把它和第一个交换。

    在有序的情况下插入排序都比快速排序快很多,而且在数据量大的时候会发生栈溢出,是一直递归开辟空间而导致的。

    三数取中就是在数组中找到一个值不是最大也不是最小。

    三数取中代码实现:

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

    代码分析:

    就是在数组的最左右边及它们和一半值共三个数,然后比较三个值找到处于中间值的下标,这个值一定不是最小或者最大,比较数组的值返回下标,这样尽管处理有序数组也不会有栈溢出的风险,也能提高运行速率。

     3.2小区间优化

    因为在二叉树中知道越到后面递归的次数越多,最下面的一层是占总的约一半,而快速排序也有这样的问题,在区间小的时候递归会非常多,所以在小区间就可以用其他的排序方法来代替,但区间小于一个范围时可以采用插入排序来排。

    代码实现:

    1. void QuickSort(int* a,int left, int right)
    2. {
    3. if (left >= right)
    4. return;
    5. if ((right - left + 1) < 10)
    6. {
    7. InsertSort(a+left, right - left + 1);
    8. }
    9. else
    10. {
    11. int begin = left;
    12. int end = right;
    13. int keyi = GetMidi(a, left, right);
    14. while (begin < end)
    15. {
    16. while (begin < end && a[keyi] <= a[end])
    17. {
    18. --end;
    19. }
    20. while (begin < end && a[keyi] >= a[begin])
    21. {
    22. ++begin;
    23. }
    24. swap(&a[begin], &a[end]);
    25. }
    26. swap(&a[keyi], &a[begin]);
    27. keyi = begin;
    28. QuickSort(a, left, keyi - 1);
    29. QuickSort(a, keyi + 1, right);
    30. }
    31. }

    这里需要注意的是用插入排序时参数应该是a+left而不是a,因为在递归过程中数组已经被分成很多个区间了(逻辑上),插入排序的是指定位置且指定大小排。 

    4.关于快速排序的细节

    在代码实现里是让R先走的,R停下来才到L走,都停下来才交换,最后在交换L和key的值,为什么key的值一定比L与R相遇的位置对应的值小呢?

    下面是分析:

    1.L遇R:R先走然后找到了比key小的值停下来了,L开始走,但是没有找到比key大的值,最后于R相遇,此时相遇的地方对应的值是比key小的。

    2.R遇L:R找到比key小的值停下来,L也遇到比key大的值停下来了,然后交换,此时R继续走,但是没有找到比key小的值了,就会一直走与L相遇,此时L所在的位置是上一次交换完的位置,也就是说L的位置的值原本是在R上一次停下来的位置对应的值,而这个值是一定比key小的,不然R是不会停下来的。

    综上可知,最后L所在的位置是一定小于key对应的值的。

    5.总代码:

    1. #include
    2. #include
    3. #include
    4. void swap(int* p1, int* p2)
    5. {
    6. int tmp = *p1;
    7. *p1 = *p2;
    8. *p2 = tmp;
    9. }
    10. void InsertSort(int* a, int n)
    11. {
    12. for (int i = 0; i < n - 1; i++)
    13. {
    14. int end = i;
    15. int tmp = a[end + 1];
    16. while (end >= 0)
    17. {
    18. if (tmp < a[end])
    19. {
    20. a[end + 1] = a[end];
    21. end--;
    22. }
    23. else
    24. break;
    25. }
    26. a[end + 1] = tmp;
    27. }
    28. }
    29. int GetMidi(int*a,int left, int right)
    30. {
    31. int midi = (left + right) / 2;
    32. if (a[midi] < a[right])
    33. {
    34. if (a[midi] > a[left])
    35. {
    36. return midi;
    37. }
    38. else
    39. {
    40. if (a[left] > a[right])
    41. return right;
    42. else
    43. return left;
    44. }
    45. }
    46. else//a[midi]>a[right]
    47. {
    48. if (a[midi] > a[left])
    49. {
    50. if (a[right] > a[left])
    51. return right;
    52. else
    53. return left;
    54. }
    55. else
    56. return midi;
    57. }
    58. }
    59. void QuickSort(int* a,int left, int right)
    60. {
    61. if (left >= right)
    62. return;
    63. if ((right - left + 1) < 10)
    64. {
    65. InsertSort(a+left, right - left + 1);
    66. }
    67. else
    68. {
    69. int begin = left;
    70. int end = right;
    71. int keyi = GetMidi(a, left, right);
    72. while (begin < end)
    73. {
    74. while (begin < end && a[keyi] <= a[end])
    75. {
    76. --end;
    77. }
    78. while (begin < end && a[keyi] >= a[begin])
    79. {
    80. ++begin;
    81. }
    82. swap(&a[begin], &a[end]);
    83. }
    84. swap(&a[keyi], &a[begin]);
    85. keyi = begin;
    86. QuickSort(a, left, keyi - 1);
    87. QuickSort(a, keyi + 1, right);
    88. }
    89. }
    90. void TestOP()
    91. {
    92. srand(time(0));
    93. const int N = 100000;
    94. int* a1 = (int*)malloc(sizeof(int) * N);
    95. int* a2 = (int*)malloc(sizeof(int) * N);
    96. int* a3 = (int*)malloc(sizeof(int) * N);
    97. int* a4 = (int*)malloc(sizeof(int) * N);
    98. int* a5 = (int*)malloc(sizeof(int) * N);
    99. int* a6 = (int*)malloc(sizeof(int) * N);
    100. int* a7 = (int*)malloc(sizeof(int) * N);
    101. for (int i = 0; i < N; ++i)
    102. {
    103. // Öظ´²»¶à
    104. a1[i] = rand() + i;
    105. // Öظ´½Ï¶à
    106. //a1[i] = rand();
    107. a2[i] = a1[i];
    108. a3[i] = a1[i];
    109. a4[i] = a1[i];
    110. a5[i] = a1[i];
    111. a6[i] = a1[i];
    112. a7[i] = a1[i];
    113. }
    114. int begin1 = clock();
    115. InsertSort(a1, N);
    116. int end1 = clock();
    117. int begin5 = clock();
    118. QuickSort(a1, 0, N - 1);
    119. int end5 = clock();
    120. printf("InsertSort:%d\n", end1 - begin1);
    121. printf("QuickSort:%d\n", end5 - begin5);
    122. free(a1);
    123. free(a2);
    124. free(a3);
    125. free(a4);
    126. free(a5);
    127. free(a6);
    128. free(a7);
    129. }
    130. int main()
    131. {
    132. int arr[] = { 11,7,5,9,6,1,4,3,8,8,8,101 };
    133. int size = sizeof(arr) / sizeof(arr[0]);
    134. //TestOP();
    135. //InsertSort(arr, size);
    136. QuickSort(arr,0,size-1);
    137. for (int i = 0; i < size; i++)
    138. {
    139. printf("%d ", arr[i]);
    140. }
    141. return 0;
    142. }

  • 相关阅读:
    【图论经典题目讲解】洛谷 P2371 墨墨的等式
    【自动化测试】selenium工具
    第 28 篇 : SSH秘钥登录
    Mybatis( If条件失效 )
    GitHub的基本使用方法
    基本数据类型----Python入门之玩转列表
    JUnit5的条件测试、嵌套测试、重复测试
    数据结构与算法学习(day3)——快速排序
    Tmux:终端复用器的基本使用(一)
    利用HbuilderX制作简单网页: HTML5期末大作业——html5漫画风格个人主页
  • 原文地址:https://blog.csdn.net/2302_80378107/article/details/139381990