快速排序及其优化

快排的原理

快排是综合性能最好的算法,也凭借优秀的性能成为常用的排序算法。快排基于分治的策略,每次选择一个基准值,将数组分为小于基准值和大于基准值两部分,然后对基准左右两侧的数组递归调用这个过程,直至找到所有元素的位置。
在这里插入图片描述
上面的图片展示了快排的基本过程,但问题是如何找到基准值的位置?最简单的想法是遍历所有元素,但这样就已经排好序了,失去了快排的意义。快排使用两端逼近的思路来找到基准值的位置,相关代码可以这样组织:

vector<int> nums//待排序数组
int pivot;   //基准值
int left;   //左边的位置
int right;  //右边的位置

while (left < right){
    while (left<right && nums[left]<=pivot) ++left;
    //找到基准值左边比基准值大的元素,交换到右边
    if (left<right) swap(nums[left], nums[right]);
    while (left<right && nums[right]>=pivot) --right;
    //找到基准值右边比基准值大的元素,交换到左边
    if (left<right) swap(nums[left], nums[right]);
}

这样就保证基准值的左边都比基准值小,右边都比基准值大了,完整的快排及测试代码如下:

#include <bits/stdc++.h>
using namespace std;

void quicksort(vector<int>& nums, int start, int end){
    if(nums.empty()) return;
    int pivot = nums[start];
    int i = start;
    int j = end;
    while (i<j){
        while (i<j && nums[j]>=pivot)--j;
        if (i<j)swap(nums[i], nums[j]);
        while (i<j && nums[i]<=pivot)++i;
        if (i<j)swap(nums[i],nums[j]);
    }
    if (start < i-1)quicksort(nums, start, i-1);
    if (end > j+1)quicksort(nums, j+1, end);
}

int main(){
    vector<int> nums = {2, 5, 9, 3, 4, 1};
    quicksort(nums, 0, nums.size()-1);
    for(int num:nums){
        cout << num << endl;
    }
    return 0;
}

执行结果如下:
在这里插入图片描述

快排复杂度

利用递归树的方式来求解快排的复杂度非常直观,下面是一次快排可能的递归树:
在这里插入图片描述
利用递归树求解,时间复杂度 = 递归树的深度 x 每层的遍历次数,显然递归树的深度为logN,每层遍历的次数总数都为n,那么快排的时间复杂度为nlogN。空间复杂度 = 递归树的深度 x 每层额外的空间大小,快排没有额外使用的空间,因此空间复杂度为logN。

快排最差复杂度

在极端情况下,快排的递归树可能不是上面的类似满二叉树的形状。在上面的讨论中,数组每次都被分为两部分,但如果基准的一侧正好没有数字,那么数据就只被分为一部分。
在这里插入图片描述
上图就是一种特殊情况,此时类似于二叉树没有左孩子,则二叉树会退化为链表,深度变为n,按照上面介绍的公式可以求到,此时时间复杂度为o(n*n),空间复杂度o(n)。

快排的优化

在快排的复杂度讨论中我们知道,基准如果选择不恰当,会导致二叉树的深度增加,这是不希望看到的,因此快排的优化策略中,通常采用随机的方式选择基准值,或者使用数组开头、正中、结尾三个元素中中间大小的元素作为基准,这样保证基准的两侧都有元素。
如果阅读过STL源码就会发现,sort()函数中,开始使用快排处理,当分组的元素个数小于某个阈值时,就改用插入排序处理。这样做的原因是当划分的区间在5~20之间时,快排效率不高,很容易出现一侧没有值的情况,而插入排序对于已经近似排好序的数组分组效果很好,因此可以采用插入排序来优化快排。在STL中,如果快排递归深度归多,还会换成堆排序来完成排序。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值