对大小为n的输入,其位置关系有n!种可能。排序算法的工作就是在所有n!种可能中找出输入究竟是哪一种。
而基于比较的排序算法手头的工具就只有 比较 操作,通过不断地比较一对对元素来排除不可能的排列(或者说留下可能的排列)。
而比较的结果只能是>和<(不考虑=)这两种,所以每一次比较操作总有机会使得一半的可能留下来。所以基于比较的排序算法的下界为Ω(lgn!) =Ω(nlgn)。
也许你可能会问那为什么插入排序在数据本来有序的情况下只需花费O(n)呢。
花费O(n)是没错,但这毕竟是决策树中最短的一支啊,下界是针对最坏情况下至少得做多少次比较。
所以快速排序快的原因就是:其分区过程在很大程度上获得的信息量很大,可以快速地排除掉不可能的排列可能。
比如拿最好的情况来说——主元为中位数(也就是均匀地把区间分为两半),那么得到有n/2个元素比主元小,n/2个元素比主元大。
这样剩下来的排列可能为:(n/2)! * (n/2)! = n!/ 2^(n/2) 种,而这一过程只花费了O(n)。
当于在O(1)的比较下就只剩根号2分之1的可能,也是呈几何下降。
当然快也是相对而言的:就拿插入排序来讲,它为什么那么慢,就是因为每一次插入元素所获得的信息很少,排除的排列可能也很少。
/*************************************************************** 函数:随机选取主元的霍尔分区算法 参数:对[low, high)范围内的数据分区,range为区间的元素个数 ***************************************************************/ int* hoarePartition(int* low, int* high, int range) { ///随机选取主元 int pivot = ((int)(((double)rand() / RAND_MAX) * range) + low); low--; while(true) { while(*(++low) < pivot); ///扫描左区间 while(*(--high) > pivot); ///扫描右区间 ///当两边都扫描到不属于自己区间的元素时 if(low < high) { ///当两区间还没有交集说明有两个元素分别落在错误的区间,交换即可 int tmp = *low; *low = *high; *high = tmp; } ///当区间相交时,Hoare分区只保证[0....pivot)中的元素 <= [pivot....n)中的元素 ///并没有确定主元的位置,所以只得到两个确定大小关系的区间,中间没有主元相隔 else return low; } }这里特别要注意的是Hoare分区算法并没有让主元隔在中间,只是得到两个确定大小关系的区间。
int cnt = 0; ///比较次数 int* hoarePartition(int* low, int* high, int range) { int pivot = *((int)(((double)rand() / RAND_MAX) * range) + low); low--; while(true) { while(*(++low) < pivot) cnt++; ///发生比较 cnt++; ///测试失败也是一次比较 while(*(--high) > pivot) cnt++; ///发生比较 cnt++; ///测试失败也是一次比较 if(low < high) { int tmp = *low; *low = *high; *high = tmp; } else return low; } } /*************************************** 函数:快速排序函数 参数:对[low, high)范围内的数据排序 平均时间复杂度:O(nlgn) ***************************************/ void quickSort(int* low, int* high) { int range = high - low; ///区间元素个数 ///当区间有不止1个元素时才进行分区 if(range > 1) { int* pivot = hoarePartition(low, high, range); ///返回主元地址 quickSort(low, pivot); ///对左区间进行快速排序 quickSort(pivot, high); } } /*********************************** 函数:测试比较次数 ***********************************/ void testQuickSort(int* a, int n) { cnt = 0; ///初始化次数 quickSort(a, a + n); cout << endl << cnt << endl; }
/*************************************************************** 函数:三数取中的霍尔分区算法 参数:对[low, high)范围内的数据分区,range为区间的元素个数 ***************************************************************/ int* hoarePartition(int* low, int* high, int range) { ///三数取中 int a = *((int)(((double)rand() / RAND_MAX) * range) + low); int b = *((int)(((double)rand() / RAND_MAX) * range) + low); int c = *((int)(((double)rand() / RAND_MAX) * range) + low); int pivot = a > b ? (b > c ? b : (a > c ? c : a)) : (a > c ? a : (b > c ? c : b)); low--; while(true) { while(*(++low) < pivot); ///扫描左区间 while(*(--high) > pivot); ///扫描右区间 ///当两边都扫描到不属于自己区间的元素时 if(low < high) { ///当两区间还没有交集说明有两个元素分别落在错误的区间,交换即可 int tmp = *low; *low = *high; *high = tmp; } ///当区间相交时,Hoare分区只保证[0....pivot)中的元素 <= [pivot....n)中的元素 ///并没有确定主元的位置,所以只得到两个确定大小关系的区间,中间没有主元相隔 else return low; } }
/*************************************** 函数:尾递归优化版快速排序 功能:对[low, high)范围内的数据排序 期望运行时间:O(2nlgn) ***************************************/ void quickSortRoutine(int* low, int* high) { int range; ///区间元素个数 ///当区间有不止1个元素时才进行分区 while((range = high - low) > 1) ///改为循环语句这样父节点就可以在下一次运算中充当孩子节点 { int* pivot = hoarePartition(low, high, range); ///返回主元地址 quickSortRoutine(low, pivot); ///对左区间进行快速排序 low = pivot; ///注意区间范围 } }
#define FACTOR 37 ///叶子的宽度 /*************************************************************** 函数:三数取中的霍尔分区算法 参数:对[low, high)范围内的数据分区,range为区间的元素个数 ***************************************************************/ int* hoarePartition(int* low, int* high, int range) { ///三数取中 int a = *((int)(((double)rand() / RAND_MAX) * range) + low); int b = *((int)(((double)rand() / RAND_MAX) * range) + low); int c = *((int)(((double)rand() / RAND_MAX) * range) + low); int pivot = a > b ? (b > c ? b : (a > c ? c : a)) : (a > c ? a : (b > c ? c : b)); low--; while(true) { while(*(++low) < pivot); ///扫描左区间 while(*(--high) > pivot); ///扫描右区间 ///当两边都扫描到不属于自己区间的元素时 if(low < high) { ///当两区间还没有交集说明有两个元素分别落在错误的区间,交换即可 int tmp = *low; *low = *high; *high = tmp; } ///当区间相交时,Hoare分区只保证[0....pivot)中的元素 <= [pivot....n)中的元素 ///并没有确定主元的位置,所以只得到两个确定大小关系的区间,中间没有主元相隔 else return low; } } /*************************************** 函数:尾递归优化版快速排序 功能:对[low, high)范围内的数据排序 期望运行时间:O(nlgn) ***************************************/ void quickSortRoutine(int* low, int* high) { int range; ///区间元素个数 ///当区间有不止1个元素时才进行分区 while((range = high - low) > FACTOR) ///改为循环语句这样父节点就可以在下一次运算中充当孩子节点 { int* pivot = hoarePartition(low, high, range); ///返回主元地址 quickSortRoutine(low, pivot); ///对左区间进行快速排序 low = pivot; ///注意区间范围 } } /******************************************************************** 函数:优化一点点的插入排序 功能:对[low, high)内的数据进行排序 ********************************************************************/ void improvedInsertionSort(int* low, int* high) { ///因为最小值在第一位,所以直接从第三个元素开始插入 ++low; while(++low < high) { int tmp = *low; ///把待插入元素保存到临时变量中 int* destPos = low; ///计算插入位子 ///把第一次测试单独提出来 if(*(--destPos) > tmp) { do { *(destPos + 1) = *destPos; }while(*(--destPos) > tmp); ///测试上一个是否是目标位置 *(destPos + 1) = tmp; ///最后一次测试失败使得destPos比实际小1 } } } /********************************** 函数:完整版快速排序 **********************************/ void quickSort(int* low, int* high) { ///设置随机数种子 srand(time(nullptr)); ///进行快速排序,叶子宽度为FACTOR quickSortRoutine(low, high); ///找出最小值放到最开始作为插入排序的哨兵节点 int* minPos = low; ///最小值的位置 int* lastPos = low + FACTOR; for(int* i = low + 1; i < lastPos; i++) if(*i < *minPos) minPos = i; int tmp = *low; *low = *minPos; *minPos = tmp; ///最后进行插入排序 improvedInsertionSort(low, high); }
原文地址:http://blog.csdn.net/u010383982/article/details/42425595