快速排序
快速排序:基本思想是通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的
直接从代码来分析:
void QuickSort(vector<int>& vec)
{
QSort(vec, 0, vec.size() - 1);
}
void QSort(vector<int>& vec, int low, int high)
{
int pivot; //记录枢轴值pivot
if (low < high)
{
pivot = Partition(vec, low, high); //将序列分割为符合要求的两部分
//算出枢轴值pivot
QSort(vec, low, pivot - 1); //对低子表递归排序
QSort(vec, pivot + 1, high); //对高子表递归排序
}
}
int Partition(vector<int>& vec, int low, int high)
{
int pivotkey;
pivotkey = vec[low]; //对子表的第一个作枢轴记录
while (low < high) //从表的两端进行扫描
{
while (low < high && vec[high] >= pivotkey) //从后找到第一个比枢轴小的值
high--;
swap(vec[low], vec[high]); //交换该值与枢轴值
while (low < high && vec[low] <= pivotkey) //从前找到第一个比枢轴大的值
low++;
swap(vec[low], vec[high]); //交换该值与枢轴值
}
return low; //返回枢轴下标
}
从代码来看,因未考虑诸多因素,所以实现比较简单,但不管其如何变化,中心思想仍然是将整个序列进行一个 ,然后再递归对每个部分进行排序,明白了这一点代码实现就比较简单了
简单的测试
测试序列:50,10,90,30,70,40,80,60,20
从结果可以看出每次 所找的枢轴值
复杂度分析
快速排序的时间性能取决于快速排序递归的深度。
- 最优情况下, 每次都划分得很均匀,如果排序n个关键字,其递归树得深度就为[ + 1],即仅需递归 次,需要时间为 的话,第一次 应该是需要对整个数组扫描一遍,做 次比较。然后,获得的枢轴将数组一分为二,那么还需要 的时间,于是不断进行划分,得出下面的不等式推断:
T(n) 2T(n/2) + n, T(1) = 0
T(n) 2(2T(n/4) + n/2) + n = 4T(n/4) + 2n
T(n) 4(2T(n/8) + n/4) + 2n = 8T(n/8) + 3n
…
T(n) nT(1) + ( ) ·n = O(n )
- 最坏情况下,待排序的序为支正序或倒序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归数画出来,他就是一颗协树,此时需要执行一个 次递归调用, 且第 次划分还需经过 次关键字的转换,也就是枢轴的位置,因此比较次数为:
= n-1 + n-2 +… + 1 =
最终其时间复杂度为O( )
平均情况下,设枢轴的关键字应该在第 的位置,那么:
T(n) = =
由数学归纳法可以证明其数量级为O(n )
就空间复杂度而言,主要递归造成的栈空间的使用,最好情况下,递归树的深度为 ,其空间复杂度也为O[ ],最坏情况,需要进行 次递归调用,其空间复杂度为 ,空间复杂度也为O[ ]
由于关键字的比较与交换是跳跃的,因此,快速排序也是一种不稳定的排序算法
快速排序的优化
1. 优化选取枢轴
采用名为三数取中的方法进行优化:
int pivotkey;
int m = low + (high - low)/2;
if (vec[low] > vec[high])
swap(vec[low], vec[high]); //使左端值较小
if (vec[m] > vec[high])
swap(vec[m], vec[high]); //使中间值较小
if (vec[m] > vec[low])
swap(vec[m], vec[low]; //使左端值较小
pivotkey = vec[low];
2. 优化不必要的交换
将交换改为替换:
int Partition(vector<int>& vec, int low, int high)
{
int pivotkey;
pivotkey = vec[low]; //对子表的第一个作枢轴记录
int tmp = pivotkey;
while (low < high) //从表的两端进行扫描
{
while (low < high && vec[high] >= pivotkey) //从后找到第一个比枢轴小的值
high--;
vec[low] = vec[high]; //交换该值与枢轴值
while (low < high && vec[low] <= pivotkey) //从前找到第一个比枢轴大的值
low++;
vec[high] = vec[low]; //交换该值与枢轴值
}
vec[low] = tmp;
return low; //返回枢轴下标
}
3. 优化小数组时的排序方案
即在小数组时采用不同的排序方法:
#define MAX_LENGTH_INSERT_SORT 7
void QSort(vector<int>& vec, int low, int high)
{
int pivot;
if ((high - row) > MAX_LENGTH_INSERT_SORT) //当high - low大于常数采用快速排序
{
pivot = Partition(vec, low, high);
QSort(vec, low, pivot - 1);
QSort(vec, pivot + 1, high);
}
else
InsertSort(vec); //小于常数采用插入排序
}
4. 优化递归操作
对 实施尾递归优化:
void QSort(vector<int>& vec, int low, int high)
{
int pivot;
if ((high - row) > MAX_LENGTH_INSERT_SORT) //当high - low大于常数采用快速排序
{
while (low < high)
{
pivot = Partition(vec, low, high);
QSort(vec, low, pivot - 1); //对低子表进行递归排序
low = pivot + 1; //尾递归
}
}
else
InsertSort(vec);
}
从实现来说,将 赋值给 ,然后将 改为了 ,这样在进行下一次循环时,得到的 实际上就相当于 ,与之前的并无差别,但这样修改实际将递归修改为了迭代,采用迭代而非递归的方法可以缩减堆栈深度,从而提高了整体性能
补充:
- 中采用的sort在早期版本里使用的是完全的 ,但后来考虑到不当的枢轴选择导致的不当分割造成算法性能下降,如今, 采用的是一种名为 (内省式排序)的算法,其行为在大部分情况下与 完全相同,但当分割行为有恶化二次行为的倾向时,能够自我侦测,转而改用 ,使其效率能维持在 的O( )
测试代码:
#include <iostream>
#include <vector>
using namespace std;
void QuickSort(vector<int>& vec);
void QSort(vector<int>& vec, int low, int high);
int Partition(vector<int>& vec, int low, int high);
void PrintStep(vector<int> vec, int n, int i);
void PrintResult(vector<int> vec, int n);
int count = 1;
void QuickSort(vector<int>& vec)
{
cout << "--------------快速排序--------------" << endl;
QSort(vec, 0, vec.size() - 1);
}
void QSort(vector<int>& vec, int low, int high)
{
int pivot;
if (low < high)
{
pivot = Partition(vec, low, high);
QSort(vec, low, pivot - 1);
QSort(vec, pivot + 1, high);
}
}
int Partition(vector<int>& vec, int low, int high)
{
int pivotkey;
pivotkey = vec[low];
while (low < high)
{
while (low < high && vec[high] >= pivotkey)
high--;
swap(vec[low], vec[high]);
while (low < high && vec[low] <= pivotkey)
low++;
swap(vec[low], vec[high]);
}
PrintStep(vec, vec.size(), count);
++count;
return low;
}
void PrintStep(vector<int> vec, int n, int i)
{
cout << "第" << i << "轮排序结果: ";
for (int j = 0; j < n; ++j)
cout << vec[j] << ' ';
cout << endl;
}
void PrintResult(vector<int> vec, int n)
{
for (int j = 0; j < n; ++j)
cout << vec[j] << ' ';
cout << endl;
}
int main(int argc, char **argv)
{
int a[] = {50,10,90,30,70,40,80,60,20};
vector<int> vec(a, a+9);
QuickSort(vec);
cout << "最终排序结果为:";
PrintResult(vec, vec.size());
return 0;
}