【算法】——会了快速排序,排序数组简直so easy

🔥前言:快速排序——分治艺术的极致演绎

在算法的璀璨星河中,快速排序(Quick Sort)犹如一颗耀眼的超新星✨。由计算机科学泰斗Tony Hoare于1959年提出的这个算法,用其优雅的分治策略和惊人的实践效率,常年霸占着受欢迎排序算法"的宝座👑。

想象一下:你面前有一堆杂乱无章的扑克牌🃏。快速排序就像一位经验丰富的荷官,每次都能精准地选出一张"王牌"(pivot),眨眼间就把整副牌分成"比王牌小"和"比王牌大"的两堆。这种"分而治之"的魔法,正是快速排序的灵魂所在!

让我们开始这段快速排序的奇妙之旅吧!🚀

(小贴士:据说Hoare爵士当年发明这个算法时,是为了更高效地排序俄语词典...看来解决实际问题永远是创新的最佳动力呢😉)

快速排序

​快速排序​​是一种基于​​分治思想​​的高效排序算法,由英国计算机科学家Tony Hoare于1959年提出。它以其优雅的实现方式和卓越的平均性能(O(n log n))成为最常用的排序算法之一,被广泛应用于各类编程语言的标准库中(如C++的std::sort、Java的Arrays.sort)。

✨ ​​核心思想:分而治之​

快速排序的核心可以概括为三个步骤:

  1. ​选基准(Pivot)​​:从数组中选取一个元素作为基准值
  2. ​分区(Partition)​​:将数组重新排列,所有比基准小的元素放在左边,比基准大的放在右边
  3. ​递归​​:对左右两个子数组重复上述过程

具体如何排序呢?最好使用三路分区策略

排序策略:三路分区快速排序

根据基准值,将数组进行三分

  • 比基准值大的值,统一放在基准值的左边
  • 和基准值一样大的值,统一放在中间
  • 比基准值小的值,统一放在基准值的右边

当然,具体怎么分,还是需要看具体场景如何使用

如何进行对三分呢?

我们可以使用三指针遍历数组

  • left指针维护左半区域
  • right指针维护右半区域
  • i指针来遍历数组

当i遇到比基准值大的元素,停下脚步,与right指向的数组元素进行交换

交换后

  • i不变,因为从right区域换过来的元素也是没有被判断的
  • right--,寻找下一个没有被判断的元素

当i遇到比基准值小的元素,停下脚步,与left指向的数组元素进行交换

交换后

  • left++,寻找下一个没有被判断的元素
  • i++,寻找下一个没有被判断的元素,因为从left区域换过来的元素一定是判断过的

当i遇到等于基准值的元素,直接i++,代表该元素不用被移动

当i>right,代表此间事了,但此刻数组并没有完全有序

例如:

  • 初始数组:[5, 3, 8, 4,4, 2, 7, 1, 10] 基准值(pivot)=4
  • 经过一次排序后,数组变成[3,2,1,4,4,7,8,10,5]
  • 此时left指向1,right指向7

因为你只是将数组进行对三分,只是相对于此时的基准值有序

因此,需要对左半区域和右半区域重复上述操作

  • 左半区域——[0,left]
  • 右半区域——[right,0]

什么时候算排序结束?

  • 直到左半区域的左边界>=右边界,代表此时左半区域只有一个,甚至没有元素
  • 直到右半区域的左边界>=右边界,代表此时右半区域只有一个,甚至没有元素

代码实现:

void qsort(vector<int>&nums,int left ,int right){
        if(left >= right ) return ;
        //开始排序
        int pos = rand()%(right -left + 1 ) + left;//随机选择避免最坏情况
        int key = nums[pos];
        int new_left = left-1;// 初始化小于区右边界
        int new_right = right+1;//初始化大于区左边界

        for(int i = left;i<new_right;){
            if(nums[i] == key) i++;
            else if(nums[i] < key){
                swap(nums[++new_left],nums[i++]);
            }
            else if(nums[i] > key){
                swap(nums[--new_right],nums[i]);
            }
        }
        qsort(nums,left,new_left);
        qsort(nums,new_right,right);
    }

快速排序解决问题

例题:排序数组

 

思路:

直接快速排序即可

代码:

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        srand(time(nullptr));
        qsort(nums,0,nums.size()-1);
        return nums;
    }
    void qsort(vector<int>&nums,int left ,int right){
        if(left >= right ) return ;
        //开始排序
        int pos = rand()%(right -left + 1 ) + left;
        int key = nums[pos];
        int new_left = left-1;
        int new_right = right+1;

        for(int i = left;i<new_right;){
            if(nums[i] == key) i++;
            else if(nums[i] < key){
                swap(nums[++new_left],nums[i++]);
            }
            else if(nums[i] > key){
                swap(nums[--new_right],nums[i]);
            }
        }
        qsort(nums,left,new_left);
        qsort(nums,new_right,right);
    }
};

例题:寻找数组中的第K大元素

思路:

要得到数组当中第k大的元素

我们需要的不是快速排序,而是快速选择

快速选择也是在快速排序的基础上寻找结果

每次快速排序会将规定数组分为三个区域,小、中、大

  • 大区域的数是数组当中最大的,令大区域的数据个数为c
  • 中区域的数是数组当中始终,且每个数相等,令小区域的数据个数为b
  • 小区域的数是数组当中最小的,令中区域的数据个数为c

如果此时c>=k

  • 那么我们要寻找的元素一定在大区域中,此时进入大区域继续寻找即可

如果此时b+c>=k

  • 那么我们要寻找的元素一定在中区域中,中区域的值是相同的,就是基准值
  • 直接return key;

如果此时b+c+a>=k

  • 那么我们要寻找的元素一定在小区域中,此时进入小区域继续寻找
  • 而一旦进入小区域,我们寻找的值将不再是第k大的值,而是第k-c-b大的值

代码实现:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        srand(time(nullptr));
        return qsort(nums,0,nums.size()-1,k);
    }
    int qsort(vector<int>& nums,int left,int right,int num){
        if(left>=right) return nums[left];
        int pos = rand()%(right-left+1)+left;
        int key = nums[pos];
        int new_left = left - 1;
        int new_right = right + 1;
        int i = left;
        while(i<new_right){
            if(nums[i]==key){
                i++;
            }else if(nums[i]>key){
                swap(nums[i],nums[--new_right]);
            }else if(nums[i]<key){
                swap(nums[i++],nums[++new_left]);
            }
        }
        int len_r = right-new_right+1;
        int len_m = new_right-1-new_left-1+1;
        if(len_r>=num) return qsort(nums,new_right,right,num);
        else if(len_r+len_m>=num) return key;
        return qsort(nums,left,new_left,num-len_m-len_r);
    }
};

🎉 ​​结语:快速排序——让数据"快"乐起来的魔法!​

恭喜你!现在你已经掌握了快速排序的终极奥义——​​"分而治之,随机应变"​​的算法哲学!🎯

快速排序就像一位​​效率狂魔的整理大师​​,面对一堆杂乱的数据,它总能以闪电般的速度(O(n log n))把它们安排得明明白白。当然,偶尔遇到"杠精"数据(比如完全逆序的数组),它也会小小地卡顿一下(O(n²)),但别担心,​​随机选基准​​就像给算法吃了颗"定定惊丸",让最坏情况变成稀有事件!

记住:

  • ​"三路分区"​​是处理重复元素的​​终极武器​​,比传统二分法更懂"去重"的痛!
  • ​递归不是洪水猛兽​​,只要基准选得好,栈溢出?不存在的!
  • ​稳定性?​​ 快速排序表示:"我快就够了,稳定交给归并排序吧!" 😎

最后,送给大家一句算法界的至理名言:

​"人生就像快速排序,选对基准(方向),才能高效前进!"​

下次当你看到std::sort在毫秒间搞定百万数据时,记得微微一笑——​​"这波我在博客里学过!"​​ 🚀

(P.S. 如果你的朋友还在用冒泡排序,请把这篇文章甩给他,并附言:​​"兄弟,该升级算法了!"​​)

​Happy Coding!​​ 🎨💻

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值