基本思想
快速排序(Quick Sort)是对冒泡排序的一种改进。
它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
假设待排序列为
{x0,x2,…,xn−1}
{
x
0
,
x
2
,
…
,
x
n
−
1
}
,首先任意选取一个记录(通常可选第一个记录)作为枢轴(pivot),然后将关键字比pivot小的都安置在它的位置之前,比pivot大的都放在它的位置之后。由此该枢轴最后所落的位置i作分界线将序列分成两个子序列
{x0,x1,…,xi−1}
{
x
0
,
x
1
,
…
,
x
i
−
1
}
和
{xi+1,xi+2…,xn−1}
{
x
i
+
1
,
x
i
+
2
…
,
x
n
−
1
}
。这个过程称为一趟快速排序(或一次划分)。
实现快速排序的步骤
- 将待排序列进行一趟快速排序,将序列一分为二。
- 对较小子序列递归排序
- 对较大子序列递归排序
一趟快速排序——交换步骤
附设两个指针iStart和iEnd,初值分别为待排序列的头和尾,枢轴的关键字为iPivotKey
1. 将枢轴记录保存起来
2. 从iEnd位置起向前搜索找到第一个关键字小于 iPivotkey的记录,对iStart指针所指位置的值覆盖
3. 从iStart位置起向后搜索找到第一个关键字大于 iPivotkey的记录,对iEnd指针所指位置的值覆盖
4. 只要符合iStart < iEnd,循环2和3步骤
5. 循环结束后,将第一步保存的枢轴记录归位,并返回枢轴位置
C++实现
/*
交换iArr中子序列[iStart, iEnd]的记录,枢轴记录归位,并返回其所在位置
此时在它之前的记录均不大于它,在它之后的记录均不小于它
*/
int Partition(int iArr[], int iStart, int iEnd)
{
// 先将枢轴记录保存
int iPivotKey = iArr[iStart];
while (iStart < iEnd)
{
// 从iEnd位置起向前搜索找到第一个关键字小于iPivotkey的记录
while (iStart < iEnd && iArr[iEnd] > iPivotKey)
{
iEnd--;
}
iArr[iStart] = iArr[iEnd];
// 从iStart位置起向后搜索找到第一个关键字大于iPivotkey的记录
while (iStart < iEnd && iArr[iStart] < iPivotKey)
{
iStart++;
}
iArr[iEnd] = iArr[iStart];
}
iArr[iStart] = iPivotKey; // 枢轴记录归位
return iStart; // 返回枢轴位置
}
void QuickSort(int iArr[], int iStart, int iEnd)
{
if (iStart < iEnd)
{
int iPivot = Partition(iArr, iStart, iEnd); // 将序列一分为二
QuickSort(iArr, iStart, iPivot - 1); // 对低子序列递归排序
QuickSort(iArr, iPivot + 1, iEnd); // 对高子序列递归排序
}
}
总结
时间复杂度
通常,快速排序被认为是,在所有同数量级
O(nlogn)
O
(
n
l
o
g
n
)
的排序方法中,其平均性能最好。但是,若初始记录按关键字有序或基本有序时,快速排序将蜕化为冒泡排序,其时间复杂度为
O(n2)
O
(
n
2
)
空间复杂度
快速排序需要一个栈空间来实现递归。
若每一趟排序都将记录序列均匀地分割成长度相接近的两个子序列,则栈的最大深度为
⌊log2n⌋+1
⌊
l
o
g
2
n
⌋
+
1
。
若每趟排序之后,枢轴位置均偏向子序列的一端,则为最坏情况,栈的最大深度为n。
最坏时间复杂度 | 最优时间复杂度 | 平均时间复杂度 | 空间复杂度 |
---|---|---|---|
O(n2) O ( n 2 ) | O(nlogn) O ( n l o g n ) | O(nlogn) O ( n l o g n ) | O(n) O ( n ) |