P1923 【深基9.例4】求第 k 小的数
题目描述
输入 n n n( 1 ≤ n < 5000000 1 \le n < 5000000 1≤n<5000000 且 n n n 为奇数)个数字 a i a_i ai( 1 ≤ a i < 10 9 1 \le a_i < {10}^9 1≤ai<109),输出这些数字的第 k k k 小的数。最小的数是第 0 0 0 小。
请尽量不要使用 nth_element
来写本题,因为本题的重点在于练习分治算法。
输入格式
输出格式
样例 #1
样例输入 #1
5 1
4 3 2 1 5
样例输出 #1
2
先看一下这个题,首先最自然想到n平方做法肯定过不了,那么采取O (n log n)
最常见的有两种做法,快速排序和归并排序
归并排序
算法原理:每次把序列平均分成两半,先逐次分配两个子序列的任务,在回溯的时候负责合并序列
代码实现:
#include<iostream>
using namespace std;
int a[5000001],medium[5000001];
void mergesort(int l,int r) //[l,r]区间
{
if(l>=r) return;
int mid=(l+r)/2;
mergesort(l,mid);
mergesort(mid+1,r);//分配任务
int ptl=l,ptr=mid+1;
int cnt=l;
while(ptl<=mid&&ptr<=r)
{
if(a[ptl]<a[ptr])
{
medium[cnt]=a[ptl];
ptl++;cnt++;
}
else
{
medium[cnt]=a[ptr];
ptr++;cnt++;
}
}//合并序列
while(ptl<=mid) {medium[cnt]=a[ptl];ptl++;cnt++;}
while(ptr<=r) {medium[cnt]=a[ptr];ptr++;cnt++;}//多出来的部分直接补在合并序列的末尾
for(int i=l;i<=r;i++) a[i]=medium[i];//数值还回原数组
}
int main()
{
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++)
cin>>a[i];
mergesort(0,n-1);
cout<<a[k];
return 0;
}
快速排序
算法原理:随机选择一个数作为基准,把小于这个数的放左边,大于的放右边,然后递归处理两个子序列
递归处理两个子序列就是分配一下任务,并不麻烦,麻烦在于怎么把数最快变成:小于基准+基准+大于基准的模式是需要考虑的
如果想要拿出来一个新的数组存放东西,那么就和归并排序一个性质了,为了能够实现数组内部通过不断交换后转化为上述模式,我们可以把基准抽出来保存好,然后剩下的元素像华容道一样调(但是基本操作是交换)
既然基本操作是交换,那么我们可以使用左右指针,第一次可以把基准数和区间最右侧的数交换,那么右指针所指位置等价于空位置
现在的策略是左指针先动,判断是否小于等于基准数,遇到反例就停下,和右侧交换,此时左侧等价于空位置(即使指向的地方本来是有数字的),那么我们现在想的更加细致了,这是一个互相扔篮球的过程,永远一个是实的位置,一个是空的位置,仔细想想也确实是这样,华容道你总不能“频分双工”吧,那么这里就是“时分双工”
那么具体过程我不再阐述,你现在可以想象这样的场景,左边蓝队,右边红队,左边场地掺有红球,右边场地掺有蓝球,然后红队、蓝队的人往中间走,永远一个人是手里空的,红队一旦发现蓝球,就扔给蓝队,并且停下作为接球者;蓝队一旦发现红球,就扔给红队,并且作为接球者。这样二人相遇的时候,就会发现,他们走过的路上都是被调整好的,而相遇点,就可以存放基准了。
可以仔细想一下,整个过程是自洽的,双方不会都停下来进入死循环的,而且扔出球的人作为接球者也十分合理,因为扔出了一个球,就意味着当前位置是空的,可以存放,而且这个人一路走来,他走过的位置都是连续的同色球。
代码实现:
#include<cstdio>
#include<cstdlib>
#include<ctime>
using namespace std;
int a[5000001],medium[5000001];
int n,k;int ans;
void swap(int &a,int &b) //手写交换,因为iostream太废物
{
int temp=a;
b=a;
a=temp;
}
void quicksort(int l,int r)
{
if(l>=r)
{
printf("%d",a[l]);
return;
}
int length=r-l+1;
int base=l+rand()%length;
int num=a[base];
swap(a[r],a[base]);//第一个空的位置在right,所以先挪动的是left指针
int ptl=l,ptr=r;
while(ptl<ptr)
{
while(a[ptl]<=num&&ptl<ptr){ptl++;}
a[ptr]=a[ptl];
while(a[ptr]>num&&ptl<ptr){ptr--;}
a[ptl]=a[ptr];
}
a[ptl]=num;
//本题只是查找,不需要完全排序
if(k<ptl) quicksort(l,ptl-1);
else if(k==ptl) {
printf("%d",a[k]);
return;
}
else quicksort(ptl+1,r);
}
int main()
{
srand((unsigned)time(NULL));
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
quicksort(0,n-1);
return 0;
}
可能要开O2优化
快速排序和归并排序谁更快?
可以问一下chatgpt谁更快: