从二叉树的角度看快速排序

快速排序本质上可以看作二叉树的前序遍历

快速排序是先将一个元素排好序,然后再将剩下的元素排好序

核心思路依然是分治

快排整体思路

准确的可以说是治分 =>  先治 得到分界点后 再分

治:双指针技巧(左右指针或者快慢指针,我会将两种都展示)将一个元素放到正确的位置

分:根据上面“治”返回的分界点进行拆分 => 得到新个较小规模问题后再治理

 你甚至可以这样理解:快速排序的过程是一个构造二叉搜索树的过程

但谈到二叉搜索树的构造,那就不得不说二叉搜索树不平衡的极端情况,极端情况下二叉搜索树会退化成一个链表,导致操作效率大幅降低

=> 为了避免这种问题 => 我们将引入洗牌算法 (别害怕就是随机数问题,没难度)

void shuffle(vector<int>& nums){
        int n = nums.size();
        srand((unsigned)time(NULL));
        for(int i=0;i<n;i++){
            int r = i + rand()%(n-i);
            swap(nums[i],nums[r]);
        }
    }

partition写法

其中最难的就是partition的写法 => 也是本文的核心内容

=> 我将从多角度分析双指针技巧

 好啦,下面就来介绍partition写法

首先你要确定以谁为基准(也就是pivot或者说分界点),这很重要 => 直接影响到下一步选择

原则:升序情况下,以最右边为基准时,最后拿较大的和基准元素交换

注意我下面所说的较大较小都是和基本比较而言的哦!

左右指针

升序:左指针找较大的数字,右指针找较小的数字,然后交换彼此

降序:左指针找较小的数字,右指针找较大的数字,然后交换彼此

升序降序分别两种写法

 辅助理解

这里是升序情况,right拿到的是较小的 => 适合基准在最左边

int Partition(vector<int>& nums, int lo, int hi) {
    int pivot = nums[lo];
    int left = lo + 1, right = hi;
    while(left <= right) {
        while(left < hi && nums[left] <= pivot) left++;
        while(right > lo && nums[right] > pivot) right--;
        if(left >= right) break;
        swap(nums[left], nums[right]);
    }
    swap(nums[lo], nums[right]);
    return right;
}

还有一种升序情况演示:

int Partition(vector<int>& nums, int lo, int hi) {
    int pivot = nums[hi];
    int left = lo, right = hi - 1;
    while(left <= right) {
        while(left < hi && nums[left] <= pivot) left++;
        while(right > lo && nums[right] > pivot) right--;
        if(left >= right) break;
        swap(nums[left], nums[right]);
    }
    swap(nums[hi], nums[left]);
    return left;
}

这里是降序情况,left拿到的是较大的 => 适合基准在最左边 (注意降序要倒一下)

int Partition(vector<int>& nums, int lo, int hi) {
    int pivot = nums[hi];
    int left = lo, right = hi - 1;
    while(left < right) {
        while(left < hi && nums[left] > pivot) left++;
        while(right > lo && nums[right] <= pivot) right--;
        if(left >= right) break;
        swap(nums[left], nums[right]);
    }
    swap(nums[hi], nums[left]);
    return left;
}

快慢指针

(这里只考虑升序情况)

不同于上面左右指针两个都可以和基准做交换,

这里fast是探路指针,只有slow可以和基准做交换

但是方向不同,在升序情况下,亦有两种写法

辅助理解 

探路fast指针遇到较大的才交换时:
        => 将所有较大的数值放到走过的背后
        => slow占的位置是较小的数字  => 适合基准在最左边

int partition(vector<int>& nums,int lo,int hi){
    int pivot = nums[lo];
    int slow = hi, fast =hi;
    while(fast > lo){
//        探路fast指针遇到较大的才交换
//        => 将所有大的数值放到走过的背后
//        => slow占的位置是较小的数字
        if(nums[fast] > pivot){
            swap(nums[slow],nums[fast]);
            slow--;
        }
        fast--;
    }
    swap(nums[slow],nums[lo]);
    return slow;
}

探路fast指针遇到较小的才交换时:
        => 将所有较小的数值放到走过的背后
        => slow占的位置是较大的数字 => 适合基准在最右边

int partition(vector<int>& nums, int lo, int hi) {
        int pivot = nums[hi];
        int fast = lo, slow = lo;
        while(fast < hi) {
            if(nums[fast] < pivot) swap(nums[slow++], nums[fast]);
            fast++;
        }
        swap(nums[hi], nums[slow]);
        return slow;
    }

最后给出完整代码(以类形式给出)

class Quick {
public:
    void sort(vector<int>& nums) {
//        为了避免极端情况,先打乱
        shuffle(nums);
        sort(nums, 0, nums.size() - 1);
    }

private:
    void sort(vector<int>& nums, int lo, int hi) {
        if(lo >= hi) return;
        int p = partition(nums, lo, hi);
        sort(nums, lo, p - 1);
        sort(nums, p + 1, hi);
    }

    int partition(vector<int>& nums, int lo, int hi) {
        int pivot = nums[hi];
        int fast = lo, slow = lo;
        while(fast < hi) {
            if(nums[fast] < pivot) swap(nums[slow++], nums[fast]);
            fast++;
        }
        swap(nums[hi], nums[slow]);
        return slow;
    }

    void shuffle(vector<int>& nums) {
        int n = nums.size();
        srand((unsigned )time(NULL));
        for(int i = 0; i < n; i++) {
            int r = i + rand() % (n - i);
            swap(nums[i], nums[r]);
        }
    }
};

快排变体 => 快速选择算法

215. 数组中的第K个最大元素 - 力扣(LeetCode)


 

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        shuffle(nums);
        int n = nums.size();
        // 将第k大数字 转化为升序下的第k个数字
        k = n - k;
        int left = 0, right = n - 1;
        while(left <= right) {
            int p = partition(nums, left, right);
            // p排位计较小 => 到分界点右边去找找第k个
            if(k > p) left = p + 1;
            // p排位计较大 => 到分界点左边去找找第k个
            else if(k < p) right = p - 1;
            else return nums[p];
        }
        return -1;
    }
private:
    void shuffle(vector<int>& nums) {
        int n = nums.size();
        srand((unsigned)time(NULL));
        for(int i = 0; i < n; i++) {
            int r = i + rand() % (n - i);
            swap(nums[i], nums[r]);
        }
    }
    int partition(vector<int>& nums, int lo, int hi) {
        int pivot = nums[hi];
        int fast = lo, slow = lo;
        while(fast < hi) {
            if(nums[fast] < pivot) swap(nums[slow++], nums[fast]);
            fast++;
        }
        swap(nums[hi], nums[slow]);
        return slow;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值