快速排序 (三向切分优化 + 引入插入排序优化 + 找第k大值)

快速排序(不稳定的排序算法)


基础实现


快速排序步骤:
 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值