【大学复健】常用排序算法的原理与代码

洛谷模板P1177

归并排序

使用分治思想,后序遍历,处理好左右后,用两个指针将二者合并。
关键点:使用动态内存定义 的数组,且最后要手动删除,防止内存爆炸。
关键点:动态定义时,当心越界,多来几个。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define mid ((l+r)>>1)
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;ch=getchar();
	}return cnt*f;
}
const int N=100003;

void merge(int a[],int l,int r){
	if(l>=r)return;
	merge(a,l,mid);merge(a,mid+1,r);
	int x=r-l+1;
	int *temp=new int[x+3];//重点
	for(int i=l;i<=r;i++)temp[i-l+1]=a[i];
	int i,j;i=l,j=mid+1;int cnt=0;
	while(i<=mid&&j<=r){
		if(a[i]<a[j]){
			temp[++cnt]=a[i++];
		}
		else{
			temp[++cnt]=a[j++];
		}
	}
	while(i<=mid)temp[++cnt]=a[i++];
	while(j<=r)temp[++cnt]=a[j++];
	for(int k=l;k<=r;k++)a[k]=temp[k-l+1];

	delete []temp;//重点
}int n,a[N];
signed main(){
	n=in;
	for(int i=1;i<=n;i++)a[i]=in;
	merge(a,1,n);	
	for(int i=1;i<=n;i++)cout<<a[i]<<" ";
	return 0;
}

堆排序

严格的nlogn。
首先来说明什么是堆(优先队列的一种实现方式):
一棵完全二叉树,在指定优先级的情况下,根节点一定比两个子节点都优先(比如大根堆的任何根节点都比两个子节点大)。
对于一个堆,基本操作包含:插入,删除,调用目前的最优先项。
插入:在树的末尾添加元素,并不断和父节点比较,如果优先级矛盾,就swap。
调用:输出根节点即可。
删除:只支持删除根节点(即优先度最高项),删除后,将最后一个点抓来顶替根节点,并每次和两个儿子中优先级最高的点进行交换(否则小儿子在大儿子上面产生矛盾),换到不能换为止。
复杂度的保证源于完全二叉树的性质。同时,因为是完全二叉树,我们大可以用一个数组来表示这棵树。初始编号1的情况下,u节点左儿子即u * 2,右儿子即u * 2+1。
对于排序而言,不断调用根节点并做删除操作即可。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;ch=getchar();
	}return cnt*f;
}
int n;
const int N=100003;
int a[N];
int q[N],tot;
void ins(int x){
	q[++tot]=x;int u=tot;
	while(u!=1){
		if(q[u/2]>x)swap(q[u/2],q[u]);
		else break;
		u/=2;
	}
}
void del(){
	swap(q[1],q[tot]);--tot;
	int u=1;
	while(u*2<=tot){
		int v=u*2;
		if(v+1<=tot&&q[v]>q[v+1])++v;
		if(q[u]<q[v])break;
		swap(q[u],q[v]);
		u=v;
	}
}
signed main(){
	n=in;
	for(int i=1;i<=n;i++){
		a[i]=in;//无效数组,草
		ins(a[i]); 
	}
	for(int i=1;i<=n;i++){
		printf("%d ",q[1]);del();
	}
	return 0;
}

快速排序

弱于其余二者。考虑目标升序。
每次选择一个基准数,将小的丢到左边,大的丢到右边,再递归整左右。
类似于中序遍历。
妙处:不用新开数组,自己左右互相覆盖的写法有启发性。
弱点:复杂度有波动,从n到n^2不等。所以建议使用严格nlogn的算法。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define mid ((l+r)>>1)
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;ch=getchar();
	}return cnt*f;
}
const int N=100003;
int n,a[N];
void Sort(int l,int r){
	if(l>=r)return;
	int tp=a[l];
	int i=l,j=r;
	while(i<j){
		while(j>i&&a[j]>tp)--j;
		a[i]=a[j];
		while(i<j&&a[i]<=tp)++i;
		a[j]=a[i];
	}a[i]=tp;
	Sort(l,i-1);Sort(i+1,r);
}
signed main(){
	n=in;for(int i=1;i<=n;i++)a[i]=in;
	Sort(1,n);
	for(int i=1;i<=n;i++)printf("%d ",a[i]);
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值