快速排序和归并排序

P1923 【深基9.例4】求第 k 小的数

题目描述

输入 n n n 1 ≤ n < 5000000 1 \le n < 5000000 1n<5000000 n n n 为奇数)个数字 a i a_i ai 1 ≤ a i < 10 9 1 \le a_i < {10}^9 1ai<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谁更快:
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值