分治--快速排序

分治–快速排序

在这里插入图片描述

颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i]012

在这里插入图片描述

解法(快排思想-三指针法使数组分三块):

算法思路:

类⽐数组分两块的算法思想,这⾥是将数组分成三块,那么我们可以再添加⼀个指针,实现数组分 三块。

设数组⼤⼩为 n ,定义三个指针 left, cur, right :

◦ left :⽤来标记 0 序列的末尾,因此初始化为-1 ;

◦ cur :⽤来扫描数组,初始化为 0 ;

◦ right :⽤来标记 2 序列的起始位置,因此初始化为 n 。

在 cur 往后扫描的过程中,保证:

◦ [ 0, left] 内的元素都是 0 ;

◦ [ left + 1, cur - 1] 内的元素都是 1 ;

◦ [cur, right - 1] 内的元素是待定元素;

◦ [ right, n] 内的元素都是 2 。

算法流程:

a. 初始化 cur = 0 , left = -1 , right = numsSize ;

b. 当 cur < right 的时候(因为 right 表⽰的是 2 序列的左边界,因此当 right 的时候,说明已经将所有数据扫描完毕了),⼀直进⾏下⾯循环: 根据 nums[cur] 的值,可以分为下⾯三种情况:

i. nums[cur] == 0 ;说明此时这个位置的元素需要在 换 le ft + 1 与 cur 位置的元素,并且让 cur++ (为什么可以 ++ 呢,是因为 cur 碰到 left + 1 的位置上,因此交 left++ (指向 0 序列的右边界), left + 1 位置要么是 0 ,要么是 完毕之后,这个位置的值已经符合我们的要求,因此 cur++ );

ii. nums[cur] == 1 ;说明这个位置应该在 让 cur++ ,判断下⼀个元素即可; left 和 cur ,交换 cur 之间,此时⽆需交换,直接

iii. nums[cur] == 2 ;说明这个位置的元素应该在 right - 1 与 cur 位置的元素,并且让 right - 1 的位置,因此交换 right- (指向 2 序列的左边界), cur 不变(因为交换过来的数是没有被判断过的,因此需要在下轮循环中判断)

c. 当循环结束之后:

[0, left] 表⽰ 0 序列;

[left + 1, right - 1] 表⽰ 1 序列;

[right, numsSize - 1] 表⽰ 2 序列。

代码如下:

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int i=0,left=-1,right=nums.size();
        while(i<right)
        {
            if(nums[i]==0) swap(nums[++left],nums[i++]);
            else if(nums[i]==1) i++;
            else swap(nums[--right],nums[i]);
        }
    }
};

排序数组

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

提示:

  • 1 <= nums.length <= 5 * 104
  • -5 * 104 <= nums[i] <= 5 * 104

解法(数组分三块思想+随机选择基准元素的快速排序):

算法思路:

我们在数据结构阶段学习的快速排序的思想可以知道,快排最核⼼的⼀步就是Partition(分割数 据):将数据按照⼀个标准,分成左右两部分。

如果我们使⽤荷兰国旗问题的思想,将数组划分为左中右三部分:左边是⽐基准元素⼩的数据, 中间是与基准元素相同的数据,右边是⽐基准元素⼤的数据。然后再去递归的排序左边部分和右边 部分即可(可以舍去⼤量的中间部分)

在处理数据量有很多重复的情况下,效率会⼤⼤提升。

算法流程:

随机选择基准算法流程:

函数设计:int randomKey(vector&nums,int left,int right)

a. 在主函数那⾥种⼀颗随机数种⼦;

b. 在随机选择基准函数这⾥⽣成⼀个随机数;

c. 由于我们要随机产⽣⼀个基准,因此可以将随机数转换成随机下标:让随机数%上区间⼤⼩, 然后加上区间的左边界即可。

快速排序算法主要流程:

a. 定义递归出⼝;

b.利⽤随机选择基准函数⽣成⼀个基准元素;

c.利⽤荷兰国旗思想将数组划分成三个区域;

d.递归处理左边区域和右边区域。

在这里插入图片描述

在这里插入图片描述

代码如下:

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
       srand(time(NULL));//种下一个随机数种子
       qsort(nums,0,nums.size()-1);
       return nums;
    }
    //快排
    void qsort(vector<int>&nums,int l,int r)
    {
        if(l>=r) return ;
        //数组分三块
        int key=getRandom(nums,l,r);
        int i=l,left=l-1,right=r+1;
        //快排的核心代码
        while(i<right)
        {
            if(nums[i]<key) swap(nums[++left],nums[i++]);
            else if(nums[i]==key) i++;
            else swap(nums[--right],nums[i]);
        }
        //[l,left][left+1,right-1][right,r]
        qsort(nums,l,left);
        qsort(nums,right,r);
    }
    int getRandom(vector<int>&nums,int left,int right)
    {
        int r=rand();
        return nums[r%(right-left)+left];
    }
};

数组中的第K个最大的元素

给定整数数组 nums 和整数 k,请返回数组中第 **k** 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104

堆排序:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        //将数组中的元素放入优先级队列中
        priority_queue<int> p(nums.begin(),nums.end());

        while(--k)//--k是循环k-1次
        {
            p.pop();
        }
        return p.top();
    }
};

解法(快速选择算法):

算法思路:

在快排中,当我们把数组「分成三块」之后: [l, left] [left + 1, right - 1] [right, r] ,我们可以通过计算每⼀个区间内元素的「个数」,进⽽推断出我们要找的元素是 在「哪⼀个区间」⾥⾯。

那么我们可以直接去「相应的区间」去寻找最终结果就好了。

在这里插入图片描述

快速选择排序:

class Solution 
{
 public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        srand(time(NULL));
        return qsort(nums, 0, nums.size() - 1, k);
    }
    int qsort(vector<int>& nums, int l, int r, int k)
    {
        if(l == r) return nums[l];
        // 1. 随机选择基准元素
 
        int key = getRandom(nums, l, r);
        // 2. 根据基准元素将数组分三块
 
        int left = l - 1, right = r + 1, i = l;
        while(i < right)
        {
            if(nums[i] < key) swap(nums[++left], nums[i++]);
            else if(nums[i] == key) i++;
            else swap(nums[--right], nums[i]);
        }
        // 3. 分情况讨论
 
        int c = r - right + 1, b = right - left - 1;
        if(c >= k) return qsort(nums, right, r, k);
        else if(b + c >= k) return key;
        else return qsort(nums, l, left, k - b - c);
    }
    int getRandom(vector<int>& nums, int left, int right)
    {
        return nums[rand() % (right - left + 1) + left];
    }
 };

最小的K个数

在这里插入图片描述

解法(快速选择算法):

算法思路:

在快排中,当我们把数组「分成三块」之后: [l, left] [left + 1, right - 1] [right, r] ,我们可以通过计算每⼀个区间内元素的「个数」,进⽽推断出最⼩的k个数在哪 些区间⾥⾯。

那么我们可以直接去「相应的区间」继续划分数组即可。

在这里插入图片描述

class Solution 
{
public:
    vector<int> getLeastNumbers(vector<int>& nums, int k) 
    {
        srand(time(NULL));
        qsort(nums, 0, nums.size() - 1, k);
        return {nums.begin(), nums.begin() + k};
    }
    void qsort(vector<int>& nums, int l, int r, int k)
    {
        if(l >= r) return;
        // 1. 随机选择⼀个基准元素+ 数组分三块
 
        int key = getRandom(nums, l, r);
        int left = l - 1, right = r + 1, i = l;
        while(i < right)
        {
            if(nums[i] < key) swap(nums[++left], nums[i++]);
            else if(nums[i] == key) i++;
            else swap(nums[--right], nums[i]);
        }
        // [l, left][left + 1, right - 1] [right, r]
        // 2. 分情况讨论
 
        int a = left - l + 1, b = right - left - 1;
        if(a > k) qsort(nums, l, left, k);
        else if(a + b >= k) return;
        else qsort(nums, right, r, k - a - b);
    }
    int getRandom(vector<int>& nums, int l, int r)
    {
        return nums[rand() % (r - l + 1) + l];
    }
 };

1, right - 1] [right, r]
// 2. 分情况讨论

    int a = left - l + 1, b = right - left - 1;
    if(a > k) qsort(nums, l, left, k);
    else if(a + b >= k) return;
    else qsort(nums, right, r, k - a - b);
}
int getRandom(vector<int>& nums, int l, int r)
{
    return nums[rand() % (r - l + 1) + l];
}

};


猜你喜欢

转载自blog.csdn.net/Mr_Xuhhh/article/details/143509450