1.思路
在快速排序算法的递归实现中,存在一种不太好的现象:随着递归层层深入,大量数据被分割成了小数组;快排对于大数组的划分可以迅速地将元素移动到它正确位置的附近,比如说对1024进行一次均等划分,那么某个元素可能会移动数百个单位位置,若进行4次均等划分,元素在正确位置上的概率就从1/1024骤升到1/64,考虑到64与1024的绝对数值,这是相当高的效率;然而对于小数组,快速排序的效率就不那么理想了,对于16个元素的数组,快速排序也要划分4次才能把它移动到正确的位置上,相对于之前几百个位置的移动,小数组排序一次只能移动几个单位的位置。
换句话说,快速排序对少量数据的划分远不如它对大量数据的划分这么划算,当排序进入到小数组阶段后,它将多次因为这些小数组而频繁调用自身,但获得的收益并不大。我姑且把这种现象叫做“小数组的边际效益“
因此,在待排序数组长度小于某一阈值K时(代码中K = 3),用插入排序替代快速排序,取长补短让代码效率达到最高,其中任然选用随即快速排序。
2.完整代码
#include <iostream>
#include <ctime>
using namespace std;
const int K = 3;
void InitArr(int *nArr, int nLen) { //初始化数组
//srand(time(NULL));
for(int i = 0; i < nLen; ++i) {
//nArr[i] = rand() % 100;
nArr[i] = i;
}
}
void PrintArr(int *nArr, int nLen) {
for(int i = 0; i < nLen; ++i) {
cout << nArr[i] << " ";
}
cout << endl;
}
void Swape(int *p1, int *p2) {
int nTmp = *p1;
*p1 = *p2;
*p2 = nTmp;
}
void RandomSort(int *nArr, int nLen) {
srand(time(NULL));
for(int i = 0; i < nLen; ++i) {
int nIndex = rand() % nLen;
Swape(&nArr[i], &nArr[nIndex]);
//Sleep(2000); //等待2s,更新随机种子
}
}
//递增排序
int PartitionUp(int *nArr, int nLeft, int nRight) {
int nKey = nArr[nRight];
int i = nLeft - 1;
for(int j = nLeft; j < nRight; ++j) {
if(nArr[j] < nKey) { //反正不稳定,就用<代替≤,省取相等情况下多余的交换运行时间
i++;
Swape(&nArr[j], &nArr[i]); //不稳定排序的原因 eg:1 5 8 8 6 11 7
}
}
Swape(&nArr[i + 1], &nArr[nRight]); //将主元素插入到中间
return i + 1;
}
//递减排序
int PartitionDown(int *nArr, int nLeft, int nRight) {
int nKey = nArr[nRight];
int i = nLeft - 1;
for(int j = nLeft; j < nRight; ++j) {
if(nArr[j] > nKey) {
++i;
Swape(&nArr[i], &nArr[j]); //不稳定排序的原因
}
}
Swape(&nArr[i + 1], &nArr[nRight]); //将主元素插入到中间
return i + 1;
}
int RandomPartition(int *nArr, int nLeft, int nRight) {
srand(time(NULL));
int nKey = rand() % (nRight - nLeft + 1) + nLeft; //随机选择【nLeft, nRight】内的某一个数
Swape(&nArr[nKey], &nArr[nRight]); //将选择的数与nArr[nRight]交换,即将选择的数作为Key值进行排序
return PartitionUp(nArr, nLeft, nRight);
}
void InsertSort(int *nArr, int nLeft, int nRight) {
for(int i = nLeft + 1; i <= nRight; ++i) {
int nTmp = nArr[i];
int j;
for(j = i - 1; j >= nLeft && nArr[j] > nTmp; --j) {
nArr[j + 1] = nArr[j];
}
nArr[j + 1] = nTmp;
}
}
void QuickSort(int *nArr, int nLeft, int nRight) {
if(nLeft < nRight) {
//分解
int nTmpPos = PartitionUp(nArr, nLeft, nRight);
//解决、合并
QuickSort(nArr, nLeft, nTmpPos - 1);
QuickSort(nArr, nTmpPos + 1, nRight);
}
}
void QuickSortRandom(int *nArr, int nLeft, int nRight) {
if(nLeft < nRight) {
int nTmpPos = RandomPartition(nArr, nLeft, nRight);
QuickSortRandom(nArr, nLeft, nTmpPos - 1);
QuickSortRandom(nArr, nTmpPos + 1, nRight);
}
}
void QuickInsertSortRandom(int *nArr, int nLeft, int nRight) { //主要代码
if(nLeft < nRight) {
if(nRight - nLeft > K) {
int nTmpPos = RandomPartition(nArr, nLeft, nRight);
QuickInsertSortRandom(nArr, nLeft, nTmpPos - 1);
QuickInsertSortRandom(nArr, nTmpPos + 1, nRight);
} else {
InsertSort(nArr, nLeft, nRight); //换用插入排序
}
}
}
int main() {
int Arr[1000];
int Len = 20;
InitArr(Arr, Len);
// PrintArr(Arr, Len);
RandomSort(Arr, Len);
PrintArr(Arr, Len);
// QuickSort(Arr, 0, Len - 1);
QuickInsertSortRandom(Arr, 0, Len - 1);
// InsertSort(Arr, 0, Len - 1);
PrintArr(Arr, Len);
return 0;
}
3.运行结果与算法效率同前文。