587-希尔&快排&归并&堆排-性能测试

喜欢ヅ旅行 2022-08-28 13:56 241阅读 0赞

希尔&快排&归并&堆排-性能测试

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. //堆的下沉调整
  5. void siftDown(int arr[], int i, int size)
  6. {
  7. int val = arr[i];
  8. while (i < size / 2)
  9. {
  10. int child = 2 * i + 1;
  11. if (child + 1 < size && arr[child + 1] > arr[child])
  12. {
  13. child = child + 1;
  14. }
  15. if (arr[child] > val)
  16. {
  17. arr[i] = arr[child];
  18. i = child; // i继续指向它的孩子,继续调整
  19. }
  20. else
  21. {
  22. break;
  23. }
  24. }
  25. arr[i] = val;
  26. }
  27. //堆排序
  28. void HeapSort(int arr[], int size)
  29. {
  30. int n = size - 1;
  31. //从第一个非叶子节点
  32. for (int i = (n - 1) / 2; i >= 0; i--)
  33. {
  34. siftDown(arr, i, size);
  35. }
  36. //把堆顶元素和末尾元素进行交换,从堆顶开始进行下沉操作
  37. for (int i = n; i > 0; i--)
  38. {
  39. int tmp = arr[0];
  40. arr[0] = arr[i];
  41. arr[i] = tmp;
  42. siftDown(arr, 0, i);//第三个参数,参与调整的元素的个数
  43. }
  44. }
  45. //归并过程函数 O(n)
  46. void Merge(int arr[], int l, int m, int r, int *p)
  47. {
  48. int idx = 0;
  49. int i = l;
  50. int j = m + 1;
  51. while (i <= m && j <= r)
  52. {
  53. if (arr[i] <= arr[j])
  54. {
  55. p[idx++] = arr[i++];
  56. }
  57. else
  58. {
  59. p[idx++] = arr[j++];
  60. }
  61. }
  62. while (i <= m)
  63. {
  64. p[idx++] = arr[i++];
  65. }
  66. while (j <= r)
  67. {
  68. p[idx++] = arr[j++];
  69. }
  70. //再把合并好的大段有序的结果,拷贝到原始arr数组[l,r]区间内
  71. for (i = l, j = 0; i <= r; i++, j++)
  72. {
  73. arr[i] = p[j];
  74. }
  75. }
  76. //归并排序递归接口
  77. void MergeSort(int arr[], int begin, int end, int *p)
  78. {
  79. //递归结束的条件
  80. if (begin >= end)
  81. {
  82. return;
  83. }
  84. int mid = (begin + end) / 2;
  85. // 先递
  86. MergeSort(arr, begin, mid, p);
  87. MergeSort(arr, mid + 1, end, p);
  88. // 再归并 [begin, mid] [mid+1, end] 把两个小段有序的序列,合并成大段有序的序列
  89. Merge(arr, begin, mid, end, p);
  90. }
  91. //归并排序
  92. void MergeSort(int arr[], int size)
  93. {
  94. int* p = new int[size]; // O(n)
  95. MergeSort(arr, 0, size - 1, p);
  96. delete[]p;
  97. }
  98. //快排分割处理函数
  99. int Partation(int arr[], int l, int r)
  100. {
  101. //选择基准数的优化:“三数取中”法 arr[l] arr[r] arr[(l+r)/2]
  102. //记录基准数
  103. int val = arr[l];
  104. //一次快排处理 时间:O(n) * O(logn) = O(nlogn) 空间:O(logn) 递归的深度所占用的栈内存
  105. while (l < r)
  106. {
  107. while (l < r && arr[r] > val)
  108. {
  109. r--;
  110. }
  111. if (l < r)
  112. {
  113. arr[l] = arr[r];
  114. l++;
  115. }
  116. while (l < r && arr[l] < val)
  117. {
  118. l++;
  119. }
  120. if (l < r)
  121. {
  122. arr[r] = arr[l];
  123. r--;
  124. }
  125. }
  126. //l == r的位置,就是放基准数的位置
  127. arr[l] = val;
  128. return l;
  129. }
  130. //快排的递归接口
  131. void QuickSort(int arr[], int begin, int end)
  132. {
  133. if (begin >= end)//快排递归结束的条件
  134. {
  135. return;
  136. }
  137. //优化一:当[begin, end]序列的元素个数小到指定数量,采用插入排序
  138. //if (end - begin <= 50)
  139. //{
  140. // InsertSort(arr, begin, end);
  141. //return;
  142. //}
  143. //在[begin, end]区间的元素做一次快排分割处理
  144. int pos = Partation(arr, begin, end);
  145. //对基准数的左边和右边的序列,再分别进行快排
  146. QuickSort(arr, begin, pos - 1);
  147. QuickSort(arr, pos + 1, end);
  148. }
  149. //快速排序
  150. void QuickSort(int arr[], int size)
  151. {
  152. return QuickSort(arr, 0, size - 1);
  153. }
  154. //希尔排序
  155. void ShellSort(int arr[], int size)
  156. {
  157. for (int gap = size / 2; gap > 0; gap /= 2) // 100W 19 1000W 24
  158. {
  159. for (int i = gap; i < size; i++) // O(n)
  160. {
  161. int val = arr[i];
  162. int j = i - gap;
  163. for (; j >= 0; j -= gap) // O(n)
  164. {
  165. if (arr[j] <= val)
  166. {
  167. break;
  168. }
  169. arr[j + gap] = arr[j];
  170. }
  171. arr[j + gap] = val;
  172. }
  173. }
  174. }
  175. int main()
  176. {
  177. cout << RAND_MAX << endl;
  178. const int COUNT = 100000000;
  179. int* arr = new int[COUNT];
  180. int* brr = new int[COUNT];
  181. /*int* crr = new int[COUNT];
  182. int* drr = new int[COUNT];*/
  183. srand(time(NULL));
  184. //0 - 32767 32768 - 32768+32767
  185. for (int i = 0; i < COUNT; i++)
  186. {
  187. int val = rand() % COUNT; // 0 - 32767
  188. arr[i] = val;
  189. }
  190. clock_t begin, end;
  191. memcpy(brr, arr, COUNT * sizeof(int));
  192. begin = clock();
  193. QuickSort(brr, COUNT);//快排
  194. end = clock();
  195. cout << "QuickSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl;
  196. memcpy(brr, arr, COUNT * sizeof(int));
  197. begin = clock();
  198. MergeSort(brr, COUNT);//归并
  199. end = clock();
  200. cout << "MergeSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl;
  201. memcpy(brr, arr, COUNT * sizeof(int));
  202. begin = clock();
  203. ShellSort(brr, COUNT);//希尔
  204. end = clock();
  205. cout << "ShellSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl;
  206. memcpy(brr, arr, COUNT * sizeof(int));
  207. begin = clock();
  208. HeapSort(brr, COUNT);//堆排
  209. end = clock();
  210. cout << "HeapSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl;
  211. }

在这里插入图片描述
我们测出:
快排的速度是最快的,其次是归并排序,然后是希尔排序,最后是堆排序

我们堆排和快排的差距怎么大呢?平均时间复杂度都一样呢

归并和快排的处理操作很相似,但是归并要开辟额外的内存空间,存放当前合并的有序的序列,然后拷贝到原始的序列中,这就是归并比快排慢一点点的地方。快排的最坏时间复杂度是O(n^2),但是场景是有序的序列,现实中数据量很大,有序的概率是非常低的。
希尔排序是对插入排序的优化,尤其是序列趋于有序的情况下,效率很高,但是在乱序的场景下,效率低于快排和归并排序。

堆排排序的在最坏最好和平均,时间复杂度都是O(nlogn)
但是排序是不稳定的。
但是它和快排差很多,为什么?
因为
在这里插入图片描述
CPU在从内存上加载数据的时候,并不是指令操作什么数据,它就加载什么数据。往往是根据程序的局部性运行原理,当你不管是访问指令,还是访问数据,访问当前这一条指令或者数据,接下来很有可能访问相邻的指令或者数据,所以,当CPU从内存上加载指令或者数据的时候,它会把这个指令或者数据以及相邻的指令或者数据都加载到CPU里面,当前用到的会放入CPU寄存器或者直接进入CPU的逻辑运算单元进行运算,马上会用到的都会放到CPU的缓存里,当下一次再去取相应指令或者数据的时候,如果缓存中有,就直接从缓存取,不用去内存取,缓存取的效率高于内存取。

在这里插入图片描述
也就是说,进行加载的时候,堆排更多是从内存取,而快排和归并排序是更多从缓存取。
缓存只是内存连续的一段数据,加载到CPU上。

大根堆的末尾节点本来值就比较小,然后每一趟都和堆顶元素交换,然后因为值本身就小,然后下沉肯定很多,要进行很多次的比较,要进行很多次的交换
在这里插入图片描述

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,241人围观)

还没有评论,来说两句吧...

相关阅读