随机快速排序和第k大数

随机快速排序和第k大数

快速排序

看过好几个快速排序的代码版本,都存在一个问题就是边界问题很难把握,比如大于还是大于等于,小于还是小于等于,甚至选取不同的划分元素时都会出现不同性质考虑的边界问题,在手撕的时候要是不熟练,很容易出现递归出错或者死循环的问题。

若需要了解快排的讨论可以看这篇文章

简单分治策略

算法导论中也有快速排序的章节,以前没有以为自己知道了,但是最近看MIT的算法导论公开课时,把快排又看了一遍,它的写法近乎完美地避开了稍微复杂的边界讨论,于是我将它的伪代码翻译成了cpp代码,并附上注释由此我们可以体会一下其中的思想。

int partition(int nums[],int l,int r)
{
int x=nums[r];//是x作为用于划分的元素
int i=l-1;
    //维持[l,i]小于等于x
for(int j=l;j<=r;j++)
if(nums[j]<=x)
{
i++;
swap(nums[i],nums[j]);//如果j指向的元素大于x,那么i不会移动,此时i会指向大于x的元素,直到j指向的元素小于等于x。
}
    //选取nums[r]作为划分元素,至少会出现一次交换
    return i;//[l,i]小于等于x
}
//和寻常写法不同,我们这里使用的双指针不是相向的双指针,而使用同向的双指针
void quick_sort(int nums[],int l,int r)
{
if(l>=r)
return;
int mid=partition(nums,l,r);
quick_sort(nums,l,mid-1);
quick_sort(nums,mid+1,r);
}

随机快速排序

我们以前学习快速排序的分析时,就知道,快速排序的时间复杂度是取决于划分元素的选取时,最坏的时候是n

^ 2,最好可以达到常数的复杂度,平均复杂度nlgn。看起来nlgn已经很好了,但是我们会在处理大数据量时,若是碰到比较坏的情况时,n^2得到的时间会很恐怖,因此我们需要弱化较坏情况的产生。那么该怎么优化呢?

我们注意到,在上诉朴素快排中,我们始终选取当前区间最后一个元素作为排序的划分元素,那么我们是否可以随机选取区间内的其他元素作为划分元素,如果这样做的话,那么我们会发现,我们减少了较好情况的产生和较差情况的产生,增加了中间情况的产生,这样就让快排更加稳定一些,虽然说快速排序的较好情况少了,但是却极大程度避免了接近n^2复杂度的产生。

怎么实现?其实我们只需要加一个随机的选取就行,其他的基本和前面的快排没有变化,我们来看代码

int partition(int nums[],int l,int r)
{
int x=nums[r];
int i=l-1;
for(int j=l;j<=r;j++)
{
if(nums[j]<=x)
{
i++;
swap(nums[i],nums[j]);
}
}
return i;//下标<=i的时候都是小于等于x的,其中nums[i]等于x
}
int rand_partition(int nums[],int l,int r)
{
srand((unsigned)time(NULL));
int i=rand()%(r-l+1)+l;//产生l到r区间内的随机数,使其和最后一个元素r交换,再选取r作为划分元素,就达到随机快排的目的了
swap(nums[r],nums[i]);
return partition(nums,l,r);
}
void rand_quick_sort(int nums[],int l,int r)
{
if(l>=r)
return;
int mid=rand_partition(nums,l,r);
rand_quick_sort(nums,l,mid-1);
rand_quick_sort(nums,mid+1,r);
} 

利用快排思想求第k大数

注意我们是利用快排思想求第k大数不是指利用快排排序数组后求第k大数,而是在进行类似快排的过程中求出第k大数,这是个随机算法,因为我们需要保证时间复杂度相对稳定,此算法的平均时间复杂度是n

如果你仔细看了上面的快排,你会发现,每次进行递归调用下一层时,我们并不会使用mid所在位置的元素,因为它是本来就应该在这个位置上,换句话说,如果下标从1开始,这个元素就是第mid大的元素。这个信息我们可以利用,如果我们要求第k大的数,如果此时mid等于k那么我们就知道了第k大数是谁,然后退出递归,而不需要对整个数组进行排序。下面给出完整的代码及其注释

#include
#include
#include
using namespace std;
const int N=100010;
int rand_partition(int nums[],int l,int r)
{
srand((unsigned)time(NULL));
int i=rand()%(r-l+1)+l;
swap(nums[r],nums[i]);
return partition(nums,l,r);
}
void rand_quick_sort_knum(int nums[],int l,int r,int k)
{
if(l>=r)
return;
int mid=rand_partition(nums,l,r);
    //下面这个代码有点类似于二分
if(mid==k)
return ;
else if(mid>k)//在数组左边找
rand_quick_sort_knum(nums,l,mid-1,k);
else//在数组右边找
rand_quick_sort_knum(nums,mid+1,r,k);
}
int main()
{
    int nums[N]={0};
int n;
int k;
cin>>n>>k;
for(int i=0;i<n;i++)
cin>>nums[i];
rand_quick_sort_knum(nums,0,n-1,k);
cout<<nums[k];//此时nums[k]就是第k大数,直接输出
return 0;
}

你可能感兴趣的:(笔记,基础算法,算法,排序算法,c++)