特别说明:
对于算法,重在理解其思想、解决问题的方法,思路。因此,以下内容全都假定待排序序列的存储结构为:顺序存储结构。
快速排序介绍
快速排序算法相较于插入、冒泡、选择排序来说而言要稍微复杂些。其主要用的是分治思想,将问题划分为更小的子问题来解决。因此,快速排序的思想其实很简单。在(目前的)时间复杂度为 的排序算法中,快速排序的系数是最小的。因此,在平均情况下,快速排序算法是被认为最快的一种排序算法(要不怎么称之为快速排序呢?)。
快速排序算法在大数据量情况下,实践证明在平均情况下的排序算法能相较于其他排序算法更好地工作、排序更为高效,且其平均情况下的时间复杂度为 ,如果在数据规模较小的情况下,反而不一定能胜过其他的简单排序算法。而且由于使用分治递归的思想,其空间复杂度也要求较高。
快速排序思想
如前所述,快速主要应用分治递归思想,将原始问题逐渐划分为更小规模的子问题解决。假设待排序数据为 ,则算法的思想描述如下:
01.设置标记 = n - 1;
// 说明:下面 02、03、04 是一次完整的划分处理
02.如果 ,则执行后面的 03 步骤;否则退出该层递归;
03.在序列 (其实该元素称为主元元素);
04.将序列 ,使得满足如下条件:
对于任何 );
注意:其实划分完成后, ;
05.将 位置索引返回;
06.设置 ,跳转并执行 02 步骤;
07.设置 + 1,跳转并执行 02 步骤;
以上便是快速排序算法的思想,其实真的很简单,就是不断地拆分,将较大规模的待排序序列分成规模更小一些的两段子序列。随着拆分的不断进行,最终都会被拆分到只有一个或0个元素的两个子序列,对于这两种子序列,它们都是有序的,因此最终实现原始数据的正确排序。
根据以上思想描述,有两个问题需要解决:
a) 划分子序列的终止条件是什么?
由上述描述明显可知,只要 的情况的,不明白的自己好好想想)
b) 如果选定第 03 步骤的元素?
很明显,最简单的办法就是在序列 即可完成排序工作)。
因此,在选取主元元素时,多会采用三数取中法来确定主元元素。即:取 。
对于上面第 04 步骤的处理,则方法也是有多种,只要执行完该步骤后,能满足其后的条件即可。下面的编码实现中,就描述了3种实现方法来划分序列,具体可参阅具体编码。
快速排序算法编码实现
下面是C++实现版本的快速排序算法编码,仅供参考。以下面算法中,用了三种不同的划分序列的方法(即:上面算法思想问题的第 03、04 两个步骤的具体实现)。
1 // 2 // summary : 交换两个元素. 3 // in param : seqlist 序列列表 4 // in param : nIndexA 节点A索引.值范围 [0, nLen) 5 // in param : nIndexB 节点A索引.值范围 [0, nLen) 6 // return : -- 7 // !!!note : 01.以下实现均假设一切输入数据都合法.即:内部不对参数全法性进行校验,默认它们全都合法有效. 8 void swapTwoItems(int seqlist[/*nLen*/], const int nIndexA, const int nIndexB) { 9 const auto nTemp = seqlist[nIndexA]; 10 seqlist[nIndexA] = seqlist[nIndexB]; 11 seqlist[nIndexB] = nTemp; 12 } 13 14 // 15 // summary : 快排确定主元元素(索引) 16 // in param : seqlist 待排序列表.同时也是排完序列表. 17 // in param : nLowBound 当前趟的下界.值 [0, nLen) 18 // in param : nHighBound 当前趟的上界.值 [0, nLen) 19 // out param : -- 20 // return : 当前趟主元元素的索引.值 [0, nLen) 21 // !!!note : 01.以下实现均假设一切输入数据都合法.即:内部不对参数全法性进行校验,默认它们全都合法有效. 22 // 02.调用该接口,则必定要求 nLowBound < nHgihBound. 23 int getQuickSortPivotIndex(int seqlist[/*nLen*/], const int nLowBound, const int nHighBound) { 24 auto nPivot = (nLowBound + nHighBound) >> 1; 25 if (seqlist[nPivot] < seqlist[nLowBound]) { 26 if (seqlist[nPivot] < seqlist[nHighBound]) { 27 nPivot = seqlist[nLowBound] < seqlist[nHighBound] ? nLowBound : nHighBound; 28 } 29 } else { 30 if (seqlist[nPivot] > seqlist[nHighBound]) { 31 nPivot = seqlist[nLowBound] > seqlist[nHighBound] ? nLowBound : nHighBound; 32 } 33 } 34 return nPivot; 35 } 36 37 // 38 // summary : 快速排序的Partition函数. 39 // in param : seqlist 待排序列表.同时也是排完序列表. 40 // in param : nLowBound 该趟处理的下界.值 [0, nLen) 41 // in param : nHighBound 该趟处理的上界.值 [0, nLen) 42 // out param : -- 43 // return : 当前该趟处理后的分隔元素位置.即:主元元素位置.值 [0, nLen) 44 // !!!note : 01.以下实现均假设一切输入数据都合法.即:内部不对参数全法性进行校验,默认它们全都合法有效. 45 // 02.调用该接口,则必定要求 nLowBound < nHgihBound. 46 int quick_partition(int seqlist[/*nLen*/], const int nLowBound, const int nHighBound) { 47 auto nPivot = getQuickSortPivotIndex(seqlist, nLowBound, nHighBound); 48 if (nPivot != nHighBound) { 49 swapTwoItems(seqlist, nPivot, nHighBound); 50 } 51 52 auto nIndex = nLowBound; 53 auto nTemp = seqlist[nHighBound]; 54 auto nLower = nLowBound - 1; 55 for (; nIndex < nHighBound; ++nIndex) { 56 if (seqlist[nIndex] < nTemp) { 57 swapTwoItems(seqlist, ++nLower, nIndex); 58 } 59 } 60 swapTwoItems(seqlist, ++nLower, nIndex); 61 return nLower; 62 } 63 64 int quick_partition_2(int seqlist[/*nLen*/], const int nLowBound, const int nHighBound) { 65 auto nPivot = getQuickSortPivotIndex(seqlist, nLowBound, nHighBound); 66 if (nPivot != nHighBound) { 67 swapTwoItems(seqlist, nPivot, nHighBound); 68 nPivot = nHighBound; 69 } 70 71 auto nLower = nLowBound; 72 auto nHigher = nHighBound; 73 while (nLower < nHigher) { 74 while (nLower != nPivot && seqlist[nLower] < seqlist[nPivot]) ++nLower; 75 if (nLower != nPivot) { 76 swapTwoItems(seqlist, nLower, nPivot); 77 nPivot = nLower; 78 } 79 while (nHigher != nPivot && seqlist[nHigher] >= seqlist[nPivot]) --nHigher; 80 if (nHigher != nPivot) { 81 swapTwoItems(seqlist, nHigher, nPivot); 82 nPivot = nHigher; 83 } 84 } 85 86 return nPivot; 87 } 88 89 int quick_partition_3(int seqlist[/*nLen*/], const int nLowBound, const int nHighBound) { 90 auto nPivot = getQuickSortPivotIndex(seqlist, nLowBound, nHighBound); 91 92 auto nLower = nLowBound; 93 auto nHigher = nHighBound; 94 while (nLower < nHigher) { 95 while (nLower < nHigher && seqlist[nLower] < seqlist[nPivot]) ++nLower; 96 swapTwoItems(seqlist, nLower, nPivot); 97 nPivot = nLower; 98 while (nLower < nHigher && seqlist[nHigher] >= seqlist[nPivot]) --nHigher; 99 swapTwoItems(seqlist, nPivot, nHigher); 100 nPivot = nHigher; 101 } 102 103 return nPivot; 104 } 105 106 // 107 // summary : 快排递归实现. 108 // in param : seqlist 待排序列表.同时也是排完序列表. 109 // in param : nLow 该趟处理的下界.值 [0, nLen) 110 // in param : nHigh 该趟处理的上界.值 [0, nLen) 111 // out param : -- 112 // return : 当前该趟处理后的分隔元素位置.即:主元元素位置.值 [0, nLen) 113 // !!!note : 01.以下实现均假设一切输入数据都合法.即:内部不对参数全法性进行校验,默认它们全都合法有效. 114 // 02.调用该接口,则必定要求 nLow < nHigh. 115 void q_sort(int seqlist[/*nLen*/], const int nLow, const int nHigh) { 116 if (nLow < nHigh) { 117 //auto pivot = quick_partition(seqlist, nLow, nHigh); 118 //auto pivot = quick_partition_2(seqlist, nLow, nHigh); 119 auto pivot = quick_partition_3(seqlist, nLow, nHigh); 120 q_sort(seqlist, nLow, pivot - 1); 121 q_sort(seqlist, pivot + 1, nHigh); 122 } 123 } 124 125 // 126 // summary : 快排对外接口.(方便外部使用.其实效果与直接使用 q_sort() 接口是一样的) 127 // in param : seqlist 待排序列表.同时也是排完序列表. 128 // in param : nLen 列表长度 129 // out param : -- 130 // return : -- 131 // !!!note : 01.以下实现均假设一切输入数据都合法.即:内部不对参数全法性进行校验,默认它们全都合法有效. 132 // 02.排序开始前 seqlist 是无序的,排序结束后 seqlist 是有序的. 133 void quick_sort(int seqlist[/*nLen*/], const int nLen) { 134 q_sort(seqlist, 0, nLen - 1); 135 }