快速排序是一个优秀的排序算法,O(n²)和Ω(nlgn),期望运行时间:θ(nlgn)且常数因子较小。
快排运用了分治的思想:
分:将数组划分成两部分(核心,partition)
治:递归地对划分地两个子数组进行排序
合并:无需合并,因为快排是直接在数组内部进行元素交换的
以下给出Partition的几种实现方法和优化方法:
【实现一】(标准版)
①选取首元素或尾元素为主元(pivot)
②从前往后遍历,遇到不比自己大的元素就把它移到前面(同时把大的移到后面)
③将pivot与A[q]交换
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;
void quickSort(int a[], int low, int high);
int a[10] = {5,2,1,5,6,3,9,0,3,8};
int main()
{
srand(time(NULL));
quickSort(a,0,9);
for(int i=0; i<10; ++i)
cout<<a[i]<<' ';
return 0;
}
void quickSort(int a[], int low, int high)
{
if(low >= high) return;
int i = low - 1;
int j = rand() % (high - low + 1) + low;
swap(a[high], a[j]);
int flag = a[high];
for(int j = low; j < high; ++j)
if(a[j] <= flag)
swap(a[++i], a[j]);
swap(a[++i], a[high]);
quickSort(a, low, i-1);
quickSort(a, i+1, high);
}
【实现二】(Hoare划分)
①分别从前面和后面开始遍历找到一个不比主元小的数a和b,交换a和b
②一直循环到整个数组被遍历
注:最后子数组A的元素全部小于等于子数组B的元素
【优化一】(随机化快排)
方法:随机选取元素与首(尾)元素交换,然后选取首(尾)元素为pivot
意义:加强随机性,使得其成为一个真正的随机化算法,即使在元素全部逆序的情况下也能保持Θ(nlgn)的速度
【优化二】(与插入排序结合)
方法:当n≤k(可能为500)时候,改用插入排序(O(n)~O(n²))
意义:减少递归的次数,且当数组"几乎有序"时,插入排序较快
【优化三】(针对元素值相同的快速排序)
方法:
①先把比pivot(A[1])小的放到前面(A[2...q])
②从q+1找和pivot相等的元素,跳过
③从数组尾向前遍历,将与pivot相等的元素转移到中间
意义:避免较多元素相等时导致复杂度骤增为θ(n²)
缺点:增加了比较的次数
pivot_t partition(int A[], int p, int r) {
int x = A[r - 1],
q = p,
t,
tmp;
for (int i = p; i < r - 1; i++) {
if (A[i] < x) {
EXCHANGE(A[q], A[i]);
q++;
}
}
for (t = q; t < r && A[t] == x; t++);
for (int i = r - 1; i >= t; i--) {
if (A[i] == x) {
EXCHANGE(A[t], A[i]);
t++;
}
}
pivot_t result = {q, t};
return result;
}
【优化四】(尾递归技术)
方法:将对右数组的递归调用转化为一个循环结构
意义:(不太清楚,好像是减少下栈深度)
TAIL-RECURSIVE-QUICKSORT(A, p, r)
while p < r
// Partition and sort left subarray
q = PARTITION(A, p, r)
TAIL-RECURSIVE-QUICKSORT(A, p, q - 1)
p = q + 1
【优化五】(三数取中划分)
方法:取三个数的中位数作为pivot
意义:增大"好的划分"的概率