• 数据结构-快速排序


    目录

    概念

    递归

    hoare法

    前后指针法

    填坑法

    非递归

    栈 实现非递归

    队列 实现非递归

    快排优化

    三数取中

    ​​​​​​​​小区间优化 

    快排性能   

    代码


    概念

       快速排序的基本思想是分治法,在待排序元素中任取一个元素作为基准,通过一定的操作使得一边的元素都小于该基准值,而另一边的元素都大于该基准值。通过这一趟排序划分被选为基准值的元素放在了最终正确的位置。然后对该基准值的左右两边元素分别递归地重复上述过程,直到左右两边的区间只有一个元素或者区间不存在,也就是待排序的所有元素都放在了其最终位置上。

    递归

    hoare法

    升序:

       选最左边的元素作为基准值key,left和right最开始分别为区间第一个元素下标和区间最后一个元素下标。

       右边right先走,找到比key小的元素就停下来,紧接着左边left再走,找到比key大的元素就停下来,然后将left,right对应位置的元素交换。

       依次往复,如果left与right相遇则将key位置的元素与相遇位置的元素进行交换。然后再递归key元素的左右区间。

       直到左右两边的区间只有一个元素或者区间不存在,也就是待排序的所有元素都放在了其最终位置上,快速排序完成。

    1. void QuickSort(SortData* a, int n)
    2. {
    3. _QuickSort1(a,0,n-1);
    4. }

    前后指针法

    升序:

       选[ begin , end ]区间的第一个元素作为基准值key,prev为区间第一个元素的位置,curr为区间第二个元素。

       curr从第二个位置开始向后遍历该区间,如果遇到比基准值key小的元素,则先让prev位置加一,然后交换prev与curr位置的元素,curr继续遍历。

       当curr遍历完该区间时,将key位置的元素与prev位置的元素进行交换。然后递归处理key左右区间的元素。

    1. void QuickSort(SortData* a, int n)
    2. {
    3. _QuickSort2(a,0,n-1);
    4. }

    填坑法

    升序:

       对于区间[begin , end],选区间第一个元素作为基准值key,保存key位置的元素,并记录key位置为最开始的坑位hole。

       right从最后一个元素往前找比基准值key小的值,找到之后将该位置的元素填入坑位hole,填入之后更新坑位hole为right位置。

       接着left从第一个元素往后找比基准值key大的值,找到之后将该位置的元素填入坑位hole,填入之后更新坑位hole为left位置。再接着right找小填坑,left找到大填坑。

       如果left与right相遇则将保存好的基准值key填入相遇的坑位上。完成之后递归处理key的左右区间。

    1. void QuickSort(SortData* a, int n)
    2. {
    3. _QuickSort3(a,0,n-1);
    4. }

    非递归

    栈 实现非递归

       先将要排序的整个区间[ begin , end ]的范围入栈,然后判断栈是否为空进入循环,进入循环之后出栈取出区间范围进行一趟快排。

       一趟快排结束之后,判断key位置的左右区间是否存在,如果存在则将左右区间的范围入栈,继续循环。

    1. void QuickSortNonR(SortData* a, int n)
    2. {
    3. _QuickSortNonRStack(a, 0, n - 1);
    4. }

    队列 实现非递归

       先将要排序的整个区间[ begin , end ]的范围入队列,然后判断队列是否为空进入循环,进入循环之后出队列取出区间范围进行一趟快排。

       一趟快排结束之后,判断key位置的左右区间是否存在,如果存在则将左右区间的范围入队列,继续循环。

    1. void QuickSortNonR(SortData* a, int n)
    2. {
    3. _QuickSortNonRQueue(a, 0, n - 1);
    4. }

    快排优化

    三数取中

       在区间的第一个位置,中间位置,最后位置的元素中选出中间大的元素,让中间大的元素被选为基准值key。 

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

    ​​​​​​​小区间优化 

       小区间优化主要是针对快速排序的递归版本,其思想是如果某一趟区间的长度小于某一长度则不需要继续进行递归快排,而是使用其他排序将该区间的元素排序好。(避免栈溢出?或者递归太深?降低空间复杂度?) 

    快排性能   

    • 空间复杂度:最好是O(log 2 N),最坏是O(N),平均是O(log 2 N)
    • 时间复杂度:最好O(N*log 2 N),最坏O(N^2)
    • 稳定性:不稳定 

    代码

    1. // 快排的优化:1,三数取中 2,小区间优化
    2. int GetMidIndex(SortData* a, int begin, int end)
    3. {
    4. int mid = (begin + end) / 2;
    5. if (a[begin] > a[end])
    6. {
    7. if (a[mid] > a[begin])
    8. {
    9. return begin;
    10. }
    11. else if (a[end]>a[mid])
    12. {
    13. return end;
    14. }
    15. else
    16. {
    17. return mid;
    18. }
    19. }
    20. else
    21. {
    22. if (a[mid] < a[begin])
    23. {
    24. return begin;
    25. }
    26. else if (a[end] < a[mid])
    27. {
    28. return end;
    29. }
    30. else
    31. {
    32. return mid;
    33. }
    34. }
    35. }
    36. // hoare法---升序
    37. void _QuickSort1(SortData* a, int begin, int end)
    38. {
    39. if (begin >= end) // 区间不存在或者区间只有一个元素返回
    40. {
    41. return;
    42. }
    43. int getMid = GetMidIndex(a, begin, end);
    44. Swap(&a[begin], &a[getMid]);
    45. int key = begin; // 区间第一个元素作为基准值
    46. int left = begin;
    47. int right = end;
    48. while (left < right)
    49. {
    50. while (left < right && a[right] >= a[key]) // 右边先找小
    51. {
    52. right--;
    53. }
    54. while (left < right && a[left] <= a[key]) // 左边再找大
    55. {
    56. left++;
    57. }
    58. Swap(&a[left], &a[right]); // 交换
    59. }
    60. Swap(&a[key], &a[left]); // key位置与相遇位置交换
    61. int mid = left;
    62. // 递归左右区间
    63. _QuickSort1(a, begin, mid-1);
    64. _QuickSort1(a, mid+1,end);
    65. }
    66. // 填坑法---升序
    67. void _QuickSort3(SortData* a, int begin, int end)
    68. {
    69. if (begin >= end)
    70. {
    71. return;
    72. }
    73. int getMid = GetMidIndex(a, begin, end);
    74. Swap(&a[begin], &a[getMid]);
    75. int left = begin;
    76. int right = end;
    77. SortData key = a[left]; // 保存基准值,区间第一个元素为基准
    78. int hole = left; // 初始坑位为基准值位置
    79. while (left < right)
    80. {
    81. while (left < right && key <= a[right])
    82. {
    83. right--; // 先右边找小
    84. }
    85. a[hole] = a[right]; // 填坑
    86. hole = right; // 更新坑位
    87. while (left < right && key >= a[left])
    88. {
    89. left++; // 再左边找大
    90. }
    91. a[hole] = a[left]; // 填坑
    92. hole = left; // 更新坑位
    93. }
    94. a[left] = key; // 保存的基准值填入相遇位置,也就是填入最新坑位
    95. int mid = left;
    96. // 递归左右区间
    97. _QuickSort3(a, begin, mid - 1);
    98. _QuickSort3(a, mid+1,end);
    99. }
    100. // 前后指针法---升序
    101. void _QuickSort2(SortData* a, int begin, int end)
    102. {
    103. if (begin >= end) // 区间不存在或者区间只有一个元素返回
    104. {
    105. return;
    106. }
    107. if (end - begin > 13)
    108. {
    109. int getMid = GetMidIndex(a, begin, end);
    110. Swap(&a[begin], &a[getMid]);
    111. int key = begin;
    112. int prev = begin;
    113. int curr = begin + 1;
    114. while (curr <= end) // 遍历
    115. {
    116. // curr小于key并且curr与prev不相等就处理
    117. if (a[curr] < a[key] && ++prev != curr)
    118. {
    119. Swap(&a[curr], &a[prev]);
    120. }
    121. curr++;
    122. }
    123. Swap(&a[key], &a[prev]); // key位置与prev位置交换
    124. int mid = prev;
    125. // 递归左右区间
    126. _QuickSort2(a, begin, mid - 1);
    127. _QuickSort2(a, mid + 1, end);
    128. }
    129. else
    130. {
    131. InsertSort(a + begin, end - begin + 1);
    132. }
    133. }
    134. void QuickSort(SortData* a, int n)
    135. {
    136. _QuickSort2(a,0,n-1);
    137. }
    138. // 栈
    139. void _QuickSortNonRStack(SortData* a, int begin,int end)
    140. {
    141. Stack s;
    142. StackInit(&s);
    143. StackPush(&s, end);
    144. StackPush(&s, begin);
    145. while (!StackEmpty(&s))
    146. {
    147. // 出栈取出区间
    148. StackDataType left = StackTop(&s);
    149. StackPop(&s);
    150. StackDataType right = StackTop(&s);
    151. StackPop(&s);
    152. int getMid = GetMidIndex(a, left, right);
    153. Swap(&a[left], &a[getMid]);
    154. int key = left;
    155. int prev = left;
    156. int curr = left + 1;
    157. while (curr <= right)
    158. {
    159. if (a[curr] < a[key] && ++prev != curr)
    160. {
    161. Swap(&a[prev], &a[curr]);
    162. }
    163. curr++;
    164. }
    165. Swap(&a[prev], &a[key]);
    166. // [left,prev-1] prev [prev+1,right]
    167. // 入栈存放区间
    168. key = prev;
    169. if (key + 1 < right)
    170. {
    171. StackPush(&s, right);
    172. StackPush(&s, key+1);
    173. }
    174. if (left < key - 1)
    175. {
    176. StackPush(&s, key-1);
    177. StackPush(&s, left);
    178. }
    179. }
    180. StackDestroy(&s);
    181. }
    182. // 队列
    183. void _QuickSortNonRQueue(SortData* a, int begin, int end)
    184. {
    185. Queue q;
    186. QueueInit(&q);
    187. QueuePush(&q, begin);
    188. QueuePush(&q, end);
    189. while (!QueueEmpty(&q))
    190. {
    191. // 出队
    192. QueueDataType left = QueueFront(&q);
    193. QueuePop(&q);
    194. QueueDataType right = QueueFront(&q);
    195. QueuePop(&q);
    196. int getMid = GetMidIndex(a, left, right);
    197. Swap(&a[left], &a[getMid]);
    198. int key = left;
    199. int prev = left;
    200. int curr = left + 1;
    201. while (curr <= right)
    202. {
    203. if (a[curr] < a[key] && ++prev != curr)
    204. {
    205. Swap(&a[prev], &a[curr]);
    206. }
    207. curr++;
    208. }
    209. Swap(&a[prev], &a[key]);
    210. // [left,prev-1] prev [prev+1,right]
    211. // 入队
    212. key = prev;
    213. if (left < key - 1)
    214. {
    215. QueuePush(&q, left);
    216. QueuePush(&q, key -1);
    217. }
    218. if (key + 1 < right)
    219. {
    220. QueuePush(&q, key +1);
    221. QueuePush(&q, right);
    222. }
    223. }
    224. QueueDestroy(&q);
    225. }
    226. void QuickSortNonR(SortData* a, int n)
    227. {
    228. //_QuickSortNonRStack(a, 0, n - 1);
    229. _QuickSortNonRQueue(a, 0, n - 1);
    230. }
  • 相关阅读:
    SpringBoot 整合MyBatisPlus
    ORACLE日期数据类型和转换
    世界上第一门编程语言究竟是谁?
    系列八、Mybatis一对多查询,只查询出了一条记录
    9月13-14日上课内容 第三章 ELK日志分析系统及部署实例
    从Android JNI的任何线程中找到FindClass
    顺序存储二叉数(Java)
    latex模板列出所有参考文献并加上超链接(xiaoming et al. 2022; xiaohong et al. 2021)
    如果在学习spring的时候没看过这份学习笔记+源码剖析,真的亏大了!
    NLP预训练模型-GPT-3
  • 原文地址:https://blog.csdn.net/m0_61433144/article/details/126506378