Partition算法详解

本文详细探讨了Partition算法,包括二分Partition和三分Partition的原理及实现细节。二分Partition在快速排序中的应用,如解决找数组中第k个最大元素的问题;三分Partition的应用举例是颜色分类问题。文章强调了正确维护循环不变量的重要性,并指出随机选择pivot能优化快速排序的时间复杂度。

partition算法有着非常重要的应用,这个算法的思想虽然简单,但具体实现的细节却比较多,今天我重点复习了这个算法,本文记录我对这个算法的理解。

Partition算法解析

二分Partition

快速排序作为非常著名的排序算法,其思想却很简单:每次从数组中选一个数作为pivot,然后将数组划分为2部分,小于等于pivot数的在其左边,大于等于pivot的数在其右边,然后分别对pivot的左边和右边进行递归。
二分partition的实现用到了双指针,left寻找一个比pivot大的数,right寻找一个比pivot小的数,然后交换它们,循环这一过程直到left == right,将pivot放到这个位置。
注意: 如果pivot选的是最左的元素,则要先移动right;如果pivot选的是最右的元素,则要先移动left。

int partition(vector<int>& nums, int left, int right) {
    int pivot = nums[left], base = left;
    while (left < right) {
        while (left < right && nums[right] >= pivot) right--;
        while (left < right && nums[left] <= pivot) left++;
        swap(nums[left], nums[right]);
    }
    nums[base] = nums[left];
    nums[left] = pivot;
    return left;
}
三分Partition

三分partition自然就是把数组划分成3部分,小于pivot的在左边,等于pivot的在中间,大于pivot的在右边。
由于三分partition要保证中间的那部分,所以像二分partition那么划分是肯定不行的。为了解决这个问题,我们需要先定义循环不变量

  • [0, zero)内的元素都等于0
  • [zero, curr)内的元素都等于1
  • [curr, two]内的元素还未被检查
  • (two, len - 1]内的元素都等于2

我们用curr来遍历数组,遍历的范围是[0, two]。在整个遍历过程中,始终要保证循环不变量的正确,因此,当遇到0时,要与nums[zero]交换,遇到2时,要与nums[two]交换。
细节: 每次交换时,边界zero或者two自然是要相应地进行移动,那么curr呢?

  • nums[curr] == 1时,显然curr++
  • nums[curr] == 0时,如果curr == zero,那么由于zero需要右移,curr则也必须右移,否则curr < zero,破坏了循环不变量;如果curr > zero,此时nums[zero] == 1,那么会把1交换到curr的位置,即使不立即curr++,下次循环时也会由于nums[curr] == 1curr++
  • nums[curr] == 2时,num[two]的值是不确定的,所以我们只进行交换和左移two,不移动curr

初始化: 初始化要保证除了[curr, two]外的3个区间都为空

二分Partition应用

我们以leetcode第215题:找数组中的第k个最大元素为例来说明二分partition算法在快速排序中的应用。
这道题虽然不是直接让我们进行快速排序,但我们可以想到,每次partition之后都会返回pivot最终在数组中的位置mid,如果mid就是要找的位置,那就不用再继续递归partition下去了,答案已经找到;否则,继续在mid的左边或者右边进行递归partition。

时间复杂度

快速排序的时间复杂度跟选取的pivot有关,如果每次都选最左边的作为pivot,而它刚好又是数组中最小的元素,那么这次partition需要遍历完整个数组,即遍历n次;接着递归右部分(有n-1个元素),如果同样最左边的是最小的,那么需要遍历n-1次…
如果每次都将数组划分成1和n-1两部分,每次又继续递归n-1的那部分,那么就会导致快排的最差时间复杂度: O ( n 2 ) O(n^2) O(n2)
为了改善这种情况,可以随机选取pivot

代码
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        srand(time(0));
        int left = 0, right = nums.size() - 1;
        int target = nums.size() - k;
        int mid = partition(nums, left, right);
        while (mid != target) {
            if (mid < target) {
                left = mid + 1;
                mid = partition(nums, left, right);
            }
            else {
                right = mid - 1;
                mid = partition(nums, left, right);
            }
        }
        return nums[mid];
    }
private:
    int partition(vector<int>& nums, int left, int right) {
        // 使用随机来加速快排, 效果明显
        int i = rand() % (right - left + 1) + left;
        swap(nums[i], nums[left]);
        int pivot = nums[left], base = left;
        while (left < right) {
            while (left < right && nums[right] >= pivot) right--;
            while (left < right && nums[left] <= pivot) left++;
            swap(nums[left], nums[right]);
        }
        nums[base] = nums[left];
        nums[left] = pivot;
        return left;
    }

    void swap(int& num1, int& num2) {
        int tmp = num1;
        num1 = num2;
        num2 = tmp;
    }
};

三分Partition应用

leetcode第75题:颜色分类就是一道典型的三分partition应用题。

代码
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        int curr = 0;
        while (curr <= right) {
            if (nums[curr] == 0) swap(nums[left++], nums[curr++]);
            else if (nums[curr] == 2) swap(nums[right--], nums[curr]);
            else curr++;
        }
    }
private:
    void swap(int& num1, int& num2) {
        int tmp = num2;
        num2 = num1;
        num1 = tmp;
    }
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值