快速排序(不稳定的排序算法)
基础实现
快速排序步骤:
1.确定分界点x,x一般选择区间的中点对应的值,也可以取其他点
2.对分界点左右区间进行处理,使得所有小于等于x的数在左半边,大于x的数在右半边
3.递归处理左右区间,若左右区间排好序,整个区间就排好序了
对某区间进行处理时,用两个指针 i 和 j 指向区间头和尾,相向移动
若遇到 i 指的值大于等于x,i 就停下来
若遇到 j 指的值小于等于x,j 就停下来
若 i 小于等于 j,交换两者指向的值,并继续移动
若 i 大于 j,此时一定满足 i 前面的数一定小于等于x,j 后面的数一定大于等于x
具体实现过程:
4 5 2 1 6 选取 x = 2,开始移动i和j
i j
4 5 2 1 6 4比x大,i停住
i j
4 5 2 1 6 6比x大,j继续移动
i j
4 5 2 1 6 1比x小,j停住,此时i小于j,交换4和1后,i继续移动
i j
1 5 2 4 6 5比x大,i停住,j移动
i j
1 5 2 4 6 2等于x,j停住,此时i小于j,交换5和2后,移动i和j
i j i和j移动一次后停止,到下面情况
1 2 5 4 6
j i 该次排序结束,递归处理区间(1,2)和(5,4,6)
区间(1,2)变成(1,2),区间(5,4,6)变成(4,5,6),再将两区间合并得:
1, 2, 4, 5, 6
*/
性能分析
归并排序和希尔排序一般比快速排序慢,因为他们在内循环中还需移动数据
快速排序的性能主要依赖于切分元素
最好的情况是每次都将快速排序对半分,此时比较次数满足Cn = 2C(n/2) + N
(N是切分元素和所有数组元素比较的成本)
最好情况下总比较次数 NlgN
而平均情况的比较次数约为 2NlnN 约为1.39NlgN,比最好情况多 39%
快速排序最多需要N^2 / 2 次比较
但随机打乱数组或巧妙选取切分元素能够预防这种情况
性能改进
优化1
切换插入排序
对于小数组,快速排序比插入排序慢
故切分到小数组时,换成插入排序来排序小数组
if (high <= low + M){
insert_sort(a, low, high);
return;
}
转换参数 M 的最佳值与系统有关,一般取 5~15
优化2
三取样切分
使用子数组的一小部分元素的中位数来切分数组(实现省略)
由于计算中位数需要时间,一般取样为3个性能最佳
优化3
三向切分
从左到右遍历数组一次,设切分元素是 v
维护一个指针 lt,使得 [low…lt-1] 的元素都小于 v
维护一个指针gt,使得 [gt+1…high] 的元素都大于 v
维护一个指针i ,使得 [lt…i-1] 中的元素都等于 v,[i…gt] 中的元素未确定
开始时 i = low
若 a[i] < v 则 swap(a[lt], a[i]) , lt ++, i ++
若 a[i] > v 则 swap(a[gt], a[i]), gt - -
若 a[i] == v 则 i ++
void sort_3way(int *a, int low, int high){
if (high <= low) return;
int lt = low, gt = high, i = low + 1;
int v = a[low];
while (i <= gt){
if (a[i] < v) swap(a[i ++], a[lt ++]);
else if (a[i] > v) swap(a[gt --], a[i]);
else i ++;
}
sort_3way(a, low, lt - 1);
sort_3way(a, gt + 1, high);
}
找第k大值
见代码
Code
#include <iostream>
using namespace std;
const int M = 7;
//切分成小数组时调用插入排序
void insert_sort(int *a, int low, int high){
if (high - low < 2) return;
for (int i = low + 1; i <= high; i ++){
int idx = i;
while (idx != low && a[idx] < a[idx - 1]){
swap(a[idx], a[idx - 1]);
idx --;
}
}
}
//普通切分
int partition(int *a, int low, int high){
//将数组切分成 a[low...i-1], a[i], a[i+1...high]
int i = low, j = high + 1;
int v = a[low]; //切分元素
while (true){
while (a[++ i] < v){
if (i == high) break;
}
while (v < a[-- j]){
if (j == low) break; //切分元素是a[low]时可以省略,他不可能比自己小
}
if (i >= j) break; //检查扫描是否结束
swap(a[i], a[j]);
}
swap(a[low], a[j]);
return j;
}
//三向切分(具体见上文)
void sort_3way(int *a, int low, int high){
if (high <= low) return;
int lt = low, gt = high, i = low + 1;
int v = a[low];
while (i <= gt){
if (a[i] < v) swap(a[i ++], a[lt ++]);
else if (a[i] > v) swap(a[gt --], a[i]);
else i ++;
}
sort_3way(a, low, lt - 1);
sort_3way(a, gt + 1, high);
}
//排序
void sort(int *a, int low, int high){
if (high <= low + M){
insert_sort(a, low, high);
return;
}
int j = partition(a, low, high);
sort(a, low, j - 1);
sort(a, j + 1, high);
}
//找第k大的值,平均时间复杂度O(n)
int select(int *a, int len, int k){
int low = 0, high = len - 1;
while (high > low){
int j = partition(a, low, high);
cout << low << ' ' << high << ' ' << j << endl;
if (j < k) low = j + 1;
else if (j > k) high = j - 1;
else return a[k];
}
return a[k];
}
int main(){
int a[10] = {-1, -99, 0, 1, 0, 21, 22, 0, -1, -99};
sort_3way(a, 0, 9);
for (int i = 0; i < 10; i ++){
cout << a[i] << ' ';
}
cout << endl << select(a, 10, 3) << endl;
return 0;
}