开幕雷击,直接狠狠当一个标题党,明天去QQ看点上班
前言
今天再一次学习了几种排序方法。
插入排序,堆排序,归并排序,快速排序等等
突发奇想
写一个测试程序把它们放上来比较一下
测试步骤:
开一个 1 0 7 10^7 107的数组,然后进行打乱,再开始排序。
参赛选手:堆排序,STL的sort,归并排序,快速排序,插入排序(太慢了没测,但也写了)
为了保证公平,我连续测试了好几次,每次结果都相差不超过200ms,基本符合事实,没有随机性。
代码如下:
这里直接展示,主要为了研究性能而非研究怎么编写程序,各位若感兴趣不妨拿来跑一下,但测试时间的这个API应该要运行在 linux 环境下。
#include <algorithm>
#include <chrono>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// heap sort
void percDown(vector<int>& a, int i, int n) {
int child;
int temp = a[i];
for (; i * 2 + 1 < n; i = child) {
child = 2 * i + 1;
if (child != n - 1 && a[child] < a[child + 1])
child++;
if (temp < a[child])
a[i] = a[child];
else
break;
}
a[i] = temp;
}
void heapsort(vector<int>& a) {
int i;
for (i = a.size() / 2; i >= 0; --i)
percDown(a, i, a.size());
for (i = a.size() - 1; i > 0; --i) {
swap(a[0], a[i]);
percDown(a, 0, i);
}
}
// insert sort
void insertsort(vector<int>& a) {
int i, j;
for (i = 1; i < a.size(); ++i) {
int temp = a[i];
for (j = i; j > 0 && temp < a[j - 1]; j--)
a[j] = a[j - 1];
a[j] = temp;
}
}
// merge sort
void mergeSort(vector<int>& a, vector<int>& temp, int left, int right);
void merge(vector<int>& a, vector<int>& temp,
int leftpos, int rightpos, int rightend);
void mergesort(vector<int>& a) {
vector<int> temp(a.size());
mergeSort(a, temp, 0, a.size() - 1);
}
void mergeSort(vector<int>& a, vector<int>& temp, int left, int right) {
if (left < right) {
int center = left + (right - left) / 2;
mergeSort(a, temp, left, center);
mergeSort(a, temp, center + 1, right);
merge(a, temp, left, center + 1, right);
}
}
void merge(vector<int>& a, vector<int>& temp,
int leftpos, int rightpos, int rightend) {
int leftend = rightpos - 1;
int temppos = leftpos;
int n = rightend - leftpos + 1;
while (leftpos <= leftend && rightpos<= rightend) {
if (a[leftpos] <= a[rightpos])
temp[temppos++] = a[leftpos++];
else
temp[temppos++] = a[rightpos++];
}
while (leftpos <= leftend)
temp[temppos++] = a[leftpos++];
while (rightpos <= rightend)
temp[temppos++] = a[rightpos++];
for (int i = 0; i < n; ++i, rightend--)
a[rightend] = temp[rightend];
}
// quicksort
int& median3(vector<int>& a, int left, int right) {
int center = left + (right - left) / 2;
if (a[center] < a[left])
swap(a[left], a[center]);
if (a[right] < a[left])
swap(a[left], a[right]);
if (a[right] < a[center])
swap(a[right], a[center]);
swap(a[right - 1], a[center]);
return a[right - 1];
}
void quickSort(vector<int>& a, int left, int right) {
if (left < right) {
int pivot = median3(a, left, right);
int i = left, j = right - 1;
for ( ; ;) {
while (a[++i] < pivot) {}
while (pivot < a[--j]) {}
if (i < j)
swap(a[i], a[j]);
else
break;
}
swap(a[i], a[right - 1]);
quickSort(a, left, i - 1);
quickSort(a, i + 1, right);
}
}
void quicksort(vector<int>& a) { quickSort(a, 0, a.size() - 1); }
void mesure(vector<int>& test, string s) {
random_shuffle(test.begin(), test.end());
auto begin = chrono::high_resolution_clock::now();
if (s == "heapsort"){
heapsort(test);
} else if (s == "syssort") {
sort(test.begin(), test.end());
} else if (s == "insertsort") {
insertsort(test);
} else if (s == "mergesort") {
mergesort(test);
} else if (s == "quicksort") {
quicksort(test);
}
auto end = chrono::high_resolution_clock::now();
cout << s << " time used: " <<
1000 * chrono::duration<double>(end - begin).count()
<< "ms\n";
}
const int TEST_SIZE = 10000000;
int main() {
vector<int> test(TEST_SIZE);
int i = 0, n;
for (; i < TEST_SIZE; ++i)
test[i] = i;
mesure(test, "heapsort");
mesure(test, "syssort");
//mesure(test, "insertsort");
mesure(test, "mergesort");
mesure(test, "quicksort");
}
结果如下
syssort 指STL的sort
这里TEST_SIZE 仅为20,结果验证了插入排序在规模很小的时候效率较好。
这里上强度,TEST_SIZE为
1
0
7
10^7
107
我跑的大小仅仅是 1 0 7 10^7 107,但插入排序已经跑不了了,作为这些算法中唯一一个 O ( n 2 ) O(n^2) O(n2)的,我电脑跑了几分钟也没跑出来,按我这个电脑一秒算 1 0 8 10^8 108次来看,估计要跑11天多才能算完。
这里也展现了算法的力量,简直amazing ! C'est très étonnant !
原因
这里我很疑惑,按理来说,STL的 sort 不应该是最快的?但我跑了好多遍都不是这个结果,不知为何。
另一方面,不难发现,堆排序是所有里面最慢的,原因有下:
- 在堆排序下滤的过程中,可以预见地产生了许多不必要的比较交换。且堆排序每一次只管取一个最大值,数据本身的顺序很容易被打破
- 堆排序基于数组实现,而在《CSAPP》中提到过这点,对数组的非顺序访问本身是CPU缓存不友好的,我认为这一点相当重要,尤其当数据量很大的时候。
相比之下,快速排序只要那个基准点选的不要太差,都能大大减少比较交换,因为被分到同一堆的数据肯定是更相近的。
而归并排序每个都会比较,但本身也只是把数组扫描一遍,不会产生过多的比较。我想这也是它稳定的原因吧,不管咋样都比一下,不多也不少刚刚好。
同时不能忽略插入排序在小数据规模时的表现,尽管小规模时时间相差无几,但次数累计上去,相信还是有时间差的。这也是为什么 STL的sort 选择当数据规模小于16时使用插入排序的原因。
总结
实验出真知,都是
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2n)的算法。快速排序却几乎碾压式的获胜,可见其威力。另一方面,归并排序作为稳定算法,亦能取得如此不错的成绩,也很牛。堆排序并不推荐,除非一堆数里我们只想取一个最小值,但那样就不是排序了。。。插入排序在小规模时效果很好(且实现很简单)
STL的sort表现平平,但我仍然认为可能是某个我没想到的原因导致这一结果,因为它本身使用快排。这个就留作以后思考吧!!
最后
献给狗狗教