#include测试结果#include using namespace std; //堆的下沉调整 void siftDown(int arr[], int i, int size) { int val = arr[i]; while (i < size / 2) { int child = 2 * i + 1; if (child + 1 < size && arr[child + 1] > arr[child]) { child = child + 1; } if (arr[child] > val) { arr[i] = arr[child]; i = child; // i继续指向它的孩子,继续调整 } else { break; } } arr[i] = val; } //堆排序 void HeapSort(int arr[], int size) { int n = size - 1; //从第一个非叶子节点 for (int i = (n - 1) / 2; i >= 0; i--) { siftDown(arr, i, size); } //把堆顶元素和末尾元素进行交换,从堆顶开始进行下沉操作 for (int i = n; i > 0; i--) { int tmp = arr[0]; arr[0] = arr[i]; arr[i] = tmp; siftDown(arr, 0, i);//第三个参数,参与调整的元素的个数 } } //归并过程函数 O(n) void Merge(int arr[], int l, int m, int r, int* p) { int idx = 0; int i = l; int j = m + 1; while (i <= m && j <= r) { if (arr[i] <= arr[j]) { p[idx++] = arr[i++]; } else { p[idx++] = arr[j++]; } } while (i <= m) { p[idx++] = arr[i++]; } while (j <= r) { p[idx++] = arr[j++]; } //再把合并好的大段有序的结果,拷贝到原始arr数组[l,r]区间内 for (i = l, j = 0; i <= r; i++, j++) { arr[i] = p[j]; } } //归并排序递归接口 void MergeSort(int arr[], int begin, int end, int* p) { //递归结束的条件 if (begin >= end) { return; } int mid = (begin + end) / 2; // 先递 MergeSort(arr, begin, mid, p); MergeSort(arr, mid + 1, end, p); // 再归并 [begin, mid] [mid+1, end] 把两个小段有序的序列,合并成大段有序的序列 Merge(arr, begin, mid, end, p); } //归并排序 void MergeSort(int arr[], int size) { int* p = new int[size]; // O(n) MergeSort(arr, 0, size - 1, p); delete[]p; } //快排分割处理函数 int Partation(int arr[], int l, int r) { //选择基准数的优化:“三数取中”法 arr[l] arr[r] arr[(l+r)/2] //记录基准数 int val = arr[l]; //一次快排处理 时间:O(n) * O(logn) = O(nlogn) 空间:O(logn) 递归的深度所占用的栈内存 while (l < r) { while (l < r && arr[r] > val) { r--; } if (l < r) { arr[l] = arr[r]; l++; } while (l < r && arr[l] < val) { l++; } if (l < r) { arr[r] = arr[l]; r--; } } //l == r的位置,就是放基准数的位置 arr[l] = val; return l; } //快排的递归接口 void QuickSort(int arr[], int begin, int end) { if (begin >= end)//快排递归结束的条件 { return; } //优化一:当[begin, end]序列的元素个数小到指定数量,采用插入排序 //if (end - begin <= 50) //{ // InsertSort(arr, begin, end); //return; //} //在[begin, end]区间的元素做一次快排分割处理 int pos = Partation(arr, begin, end); //对基准数的左边和右边的序列,再分别进行快排 QuickSort(arr, begin, pos - 1); QuickSort(arr, pos + 1, end); } //快速排序 void QuickSort(int arr[], int size) { return QuickSort(arr, 0, size - 1); } //希尔排序 void ShellSort(int arr[], int size) { for (int gap = size / 2; gap > 0; gap /= 2) // 100W 19 1000W 24 { for (int i = gap; i < size; i++) // O(n) { int val = arr[i]; int j = i - gap; for (; j >= 0; j -= gap) // O(n) { if (arr[j] <= val) { break; } arr[j + gap] = arr[j]; } arr[j + gap] = val; } } } int main() { cout << RAND_MAX << endl; const int COUNT = 100000000; int* arr = new int[COUNT]; int* brr = new int[COUNT]; srand(time(NULL)); //0 - 32767 32768 - 32768+32767 for (int i = 0; i < COUNT; i++) { int val = rand() % COUNT; // 0 - 32767 arr[i] = val; } clock_t begin, end; memcpy(brr, arr, COUNT * sizeof(int)); begin = clock(); QuickSort(brr, COUNT);//快排 end = clock(); cout << "QuickSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl; memcpy(brr, arr, COUNT * sizeof(int)); begin = clock(); MergeSort(brr, COUNT);//归并 end = clock(); cout << "MergeSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl; memcpy(brr, arr, COUNT * sizeof(int)); begin = clock(); ShellSort(brr, COUNT);//希尔 end = clock(); cout << "ShellSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl; memcpy(brr, arr, COUNT * sizeof(int)); begin = clock(); HeapSort(brr, COUNT);//堆排 end = clock(); cout << "HeapSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl; }
我们测出:
- 快排的速度是最快的,其次是归并排序,然后是希尔排序,最后是堆排序
归并和快排:
- 归并和快排的处理操作很相似,但是归并要开辟额外的内存空间,存放当前合并的有序的序列,然后拷贝到原始的序列中,这就是归并比快排慢一点点的地方。快排的最坏时间复杂度是O(n^2),但是场景是有序的序列,现实中数据量很大,有序的概率是非常低的。
希尔排序是对插入排序的优化,尤其是序列趋于有序的情况下,效率很高,但是在乱序的场景下,效率低于快排和归并排序。
堆排序和快排:
堆排排序的在最坏最好和平均,时间复杂度都是O(nlogn),但是排序是不稳定的,但是它和快排差很多,为什么?
- CPU在从内存上加载数据的时候,并不是指令操作什么数据,它就加载什么数据;
- 往往是根据程序的局部性运行原理,当你不管是访问指令,还是访问数据,访问当前这一条指令或者数据,接下来很有可能访问相邻的指令或者数据;
- 所以,当CPU从内存上加载指令或者数据的时候,它会把这个指令或者数据以及相邻的指令或者数据都加载到CPU里面,当前用到的会放入CPU寄存器或者直接进入CPU的逻辑运算单元进行运算,马上会用到的都会放到CPU的缓存里,当下一次再去取相应指令或者数据的时候,如果缓存中有,就直接从缓存取,不用去内存取,缓存取的效率高于内存取。
也就是说,进行加载的时候,堆排更多是从内存取,而快排和归并排序是更多从缓存取。(缓存只是内存连续的一段数据,加载到CPU上。)
大根堆的末尾节点本来值就比较小,然后每一趟都和堆顶元素交换,然后因为值本身就小,然后下沉肯定很多,要进行很多次的比较,要进行很多次的交换



