归并排序及求逆序对数

什么是归并排序?归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 

当我们看完这段话是好像既熟悉又陌生......

但是接着往下看,你就会发现一张图:

似乎顿时豁然开朗了起来......

归并排序不就是二分么(不会的同鞋去学习一下二分)

所谓的归并排序其实就是两个步骤:1.递归二分序列2.合并序列

其中第二点合并序列尤为重要!!

怎么去合并呢?

第一步:申请空间temp,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

第二步:设定两个指针i,j,最初位置分别为两个已经排序序列的起始位置

第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针超出序列尾

第四步:将另一序列剩下的所有元素直接复制到合并序列尾

是不是还是有一点懵.....

解释:简单的来说就是依次比较,小的元素放入中间数组temp中,当某一区间内的所有值都放入temp数组中去的时候,就将剩下区间没有进行比较的元素直接合并到temp数组中。


 

 余下的自动补齐temp即10放入temp中。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 1000001
int a[N];
int b[N];
void merge(int l,int mid,int r){
	int i=l,j=mid+1,p=1;
	while(i<=mid&&j<=r){
		if(a[i]>a[j]){
			b[p]=a[j];
			p++;
			j++;
		}
		else{
			b[p]=a[i];
			i++;
			p++;
		}
	}
	if(i>mid){
		for(int k=p;j<=r;j++,k++){
			b[k]=a[j]; 
		}	 
	}
	else if(j>r){
		for(int k=p;i<=mid;i++,k++){
			b[k]=a[i]; 
		}	 
	}
	for(int k=l,q=1;k<=r;k++,q++)a[k]=b[q];
}
void divide(int l,int r){
	if(l==r)return;
	int mid=(r+l)/2; 
	divide(l,mid);
	divide(mid+1,r);
	merge(l,mid,r);
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	divide(1,n);
	for(int i=1;i<=n;i++)cout<<a[i]<<” ”;
	return 0;
} 

扩展:

归并算法还可以计算出逆序对数

什么是逆序对?

逆序对是指在排列中某一对元素的先后次序与标准次序不同时,就说他构成一个逆序。例如(12534)中有<5,3>,<5,4>两个逆序对。

什么是逆序对数?

一个排列中所有逆序的总数。

归并算法怎么快速求逆序对数?

常规的暴力解法时间复杂度为O(n^{2}),然而运用归并排序能够将时间复杂度降为O(nlogn),当然还有树状数组的解法也是O(nlogn)

题目:逆序对 - 洛谷

思路如下:

归并算法:

同样是以上步骤,递归下来,然后是“并”操作,这里有一点不同的是,以j为中心,依次遍历比较a[j],如果a[j]>a[i],则记录数据(ans+=mid-i+1),等右字树的遍历完后,完成“并”操作。

例如

 

对于a[j]=5来说,左子树会一直遍历到a[i]=6,此时可以构成逆序对,逆序对数为mid-i+1即4-3+1=2。为什么是mid-i+1呢??

嗯..

因为归并排序中左子树和右字树中的序列一定是已经排列完的序列,比较以a[j]为第二个元素时此区间拥有多少逆序对数。即找到有多少个这样的<X,a[j]>。当第一次找到一个比a[j]大的数,那么以后的数也一定比a[j]要大(原因如上标红字体)此时逆序对数为mid-i+1;

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 1000001
int a[N];
int b[N];
int cnt=0;
void merge(int l,int mid,int r){
	int i=l,j=mid+1,p=1;
	while(i<=mid&&j<=r){
		if(a[i]>a[j]){
			cnt+=mid-i+1;
			b[p]=a[j];
			p++;
			j++;
		}
		else{
			b[p]=a[i];
			i++;
			p++;
		}
	}
	if(i>mid){
		for(int k=p;j<=r;j++,k++){
			b[k]=a[j]; 
		}	 
	}
	else if(j>r){
		for(int k=p;i<=mid;i++,k++){
			b[k]=a[i]; 
		}	 
	}
	for(int k=l,q=1;k<=r;k++,q++)a[k]=b[q];
}
void divide(int l,int r){
	if(l==r)return;
	int mid=(r+l)/2; 
	divide(l,mid);
	divide(mid+1,r);
	merge(l,mid,r);
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	divide(1,n);
	cout<<cnt;
	return 0;
} 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值