文章目录
- 算法原理
- 实现思路
- 典型例题
- 颜色分类
- 快速排序优化
- 数组中最大的K个数
- 最小的K个数
- 总结
算法原理
分治的原理就是分而治之,从原理上讲,就是把一个复杂的问题划分成子问题,再将子问题继续划分,直到可以解决
实现思路
基于分治的原理进行快速排序,区别于传统的快速排序,这里对快速排序进行改良,成为更优先的三路划分算法,可以处理一些极端场景,使快速排序的适用性更加广泛,同时引出快速选择算法,用来搭配堆排序解决topk
问题
典型例题
颜色分类
本题和前面在双指针算法中做的移动0
的解法类似,这里其实算法原理和快速排序优化的三路划分很相似,将数组中的区域划分为三个部分,分别为0,1,2
,因此解决问题的时候要先想清楚如何解决,把数据量弄清楚
对于此题来说,算法思路就是如果遇到2
,就把数据扔到最后,如果遇到0
,就放到最前,那么1
就会被天然的隔离到最中间的部分,这是可行的
快速排序优化
根据上面的原理,可以改进快速排序,快速排序的弊端在于,当他要进行处理很多相同数据的时候,就遇到十分低效的情况,基于这个原因,可以在实现它的过程中利用到一些上面的想法,这样的算法思路其实也叫做三路划分
因此可以使用这个方法来解题,否则会超时
class Solution
{
public:
vector<int> sortArray(vector<int>& nums)
{
srand(time(0));
quicksort(nums,0,nums.size()-1);
return nums;
}
void quicksort(vector<int>& nums,int left,int right)
{
if(left>=right)
{
return;
}
int key=numsrandom(nums,left,right);
int i=left,begin=left-1,end=right+1;
while(i<end)
{
if(nums[i]<key)
{
swap(nums[++begin],nums[i++]);
}
else if(nums[i]==key)
{
i++;
}
else
{
swap(nums[--end],nums[i]);
}
}
quicksort(nums,left,begin);
quicksort(nums,end,right);
}
int numsrandom(vector<int>& nums,int left,int right)
{
int keyi=rand()%(right-left+1)+left;
return nums[keyi];
}
};
基于快速排序的三路划分原理,可以引申出新的思想:快速选择问题
数组中最大的K个数
看到这个题第一思想是使用堆排序,因为堆排序处理TopK
问题是十分有效的,但是后面的限制条件,时间复杂度必须是O(N)
的算法,因此这里并不能使用TopK
算法
由于前面有快速排序的基础,因此这里可以引申出一个快速选择解法
首先看思路:
在快速排序的三路划分算法中,当划分结束后,整个数组会被天然的划分为下面三个部分:
假设,我们这里让蓝色区域的记作C,红色区域记作B,棕色区域记作A
那如果我们要求的这个第k
个数据落在蓝色区域
,那么我们只需要在C
这个单位长度内寻找一次即可,如果落在红色区域
,那么要找的这个数据就是key
,如果落在棕色区域
,则只需要在A
这个单位长度寻找一次即可
由此,就引申出了快速选择算法:基于快速排序从而引申出的快速选择
class Solution
{
public:
int findKthLargest(vector<int>& nums, int k)
{
srand(time(NULL));
return quicksort(nums,0,nums.size()-1,k);
}
int quicksort(vector<int>& nums,int l,int r,int k)
{
if(l==r)
{
return nums[l];
}
// 1. 选取中间元素
int key=getrandom(nums,l,r);
int left=l-1,right=r+1,i=l;
// 2. 三路划分
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;
int b=(right-1)-(left+1)+1;
if(c>=k)
{
return quicksort(nums,right,r,k);
}
else if(b+c>=k)
{
return key;
}
else
{
return quicksort(nums,l,left,k-b-c);
}
}
int getrandom(vector<int>& nums,int l,int r)
{
return nums[rand()%(r-l+1)+l];
}
};
时间复杂度分析较为复杂,但是是严格符合题目要求的,由此其实也看出了分治的思想核心,把一个大问题转换成小问题,直到最后转换成一个我们一下就能解决的问题,不断的缩小我们需要寻找的区间,这样最终就能找到我们需要的答案
最小的K个数
class Solution
{
public:
vector<int> getLeastNumbers(vector<int>& nums, int k)
{
srand(time(NULL));
quicksort(nums,0,nums.size()-1,k);
return {nums.begin(),nums.begin()+k};
}
void quicksort(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;
// 2. 三路划分
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 a=left-l+1,b=(right-1)-(left+1)+1;
if(a>k)
{
quicksort(nums,l,left,k);
}
else if(a+b>=k)
{
return;
}
else
{
quicksort(nums,right,r,k-a-b);
}
}
int getrandom(vector<int>& nums,int l,int r)
{
return nums[rand()%(r-l+1)+l];
}
};
对于这个题来说,解法多种多样,可以采用很多方法,topk
,直接排序,快速选择,这里依旧选择快速选择来写
原理和前面类似,由于返回的是前k
个数不一定要有序,因此三路划分后可以直接进行条件判断,满足需求就可以跳出循环
总结
分治思想用以快速排序,可以引申出快速选择
这个算法,而这个算法在实际应用中有很大的作用,对于解决前k
个数或第k
个数都有很大的算法意义,下篇会总结分治思想用以解决归并问题