快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
- 从序列中挑出一个元素,作为”基准”(pivot).
- 把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
- 对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
快排特点如下:
- 分类 ------------ 内部比较排序
- 数据结构 --------- 数组
- 最差时间复杂度 ---- 每次选取的基准都是最大(或最小)的元素,导致每次只划分出了一个分区,需要进行n-1次划分才能结束递归,时间复杂度为O(n^2)
- 最优时间复杂度 ---- 每次选取的基准都是中位数,这样每次都均匀的划分出两个分区,只需要logn次划分就能结束递归,时间复杂度为O(nlogn)
- 平均时间复杂度 ---- O(nlogn)
- 所需辅助空间 ------ 主要是递归造成的栈空间的使用(用来保存left和right等局部变量),取决于递归树的深度,一般为O(logn),最差为O(n)
- 稳定性 ---------- 不稳定
经典快排代码如下:
//bound指向<=x的下一位置 ,也就是>x的前一个位置
int partation1(int arr[], int l, int r)
{
int bound = l;
//以数组最后一个数arr[r]作为基准来分界
for (int i = l; i < r;++i)
{
if (arr[i]>arr[r])
{
continue;
}
else swap(arr[bound++],arr[i]);
}
//快速排序是不稳定的排序算法,不稳定发生下面这个swap函数处。
//比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基准元素是5,一次划分操作后5要和第一个8进行交换,从而改变了两个元素8的相对次序。
swap(arr[r], arr[bound]);
return bound;
}
void _quicksort1(int arr[], int l, int r)
{
if (l >= r)
return;
int bound=partation1(arr, l, r);
_quicksort1(arr, l, bound-1);
_quicksort1(arr, bound + 1, r);
}
上述二路快排将数组分为两部分,一部分小于等于x,一部分大于x。如果将数组分为3部分,一部分小于x,一部分等于x,一部分大于x,则效率更高。
三路快排代码如下:
// lef指向下一个<x的数该放的位置,rig指向下一个>x的数该放的位置
vector<int>partation2(int arr[], int l, int r)
{
int rig = r-1,lef=l;
//注意边界条件,什么时候停止遍历 必须把所有数都遍历到,一个不能多,一个不能少
while (l<=rig)
{
if (arr[l]>arr[r])
{
swap(arr[rig--], arr[l]);
}
else if (arr[l] < arr[r])
{
swap(arr[lef++],arr[l++]);
}
else ++l;
}
swap(arr[r],arr[++rig]);
//返回的是等于x的数在数组中的范围
vector<int>res(2, 0);
res[0] = lef;
res[1] = rig;
return res;
}
void _quicksort2(int arr[], int l, int r)
{
if (l >= r)
return;
vector<int>bound = partation2(arr, l, r);
_quicksort2(arr, l, bound[0] - 1);
_quicksort2(arr, bound[1] + 1, r);
}