算法入门-数据结构-归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。那么首先,我们了解下分治法

将一个大的问题分解成为一些较小的子问题,分别求解各个子问题,然后将各个子问题合并就可以得到问题的解。分治法和递归的思想非常类似,分治法一般是划分为若干个相等的子问题,而递归则一般是逐层减一得到最简单的子问题,接着再一层一层返回。

归并排序基本原理

通过对若干个有序结点序列的归并来实现排序,所谓归并是指将若干个已排好序的部分合并成一个有序的部分。

下面这幅图能够直观地表示这个过程:

我们可以看到,对于初始的无序数列,我们要想得到最后的有序数列,它需要不断地分割以得到若干个有序的数列,再将这些有序的数列进行归并处理,如四合二、二合一,直到最终归并为一个有序数列,那么这个数列便是排序好的结果。

而归并排序,在其中的主要工作,便是利用递归,把大的数组一半半剖开,直到它成为有序数列,再进行两个有序数列的归并操作将它们合并为一个有序数列,这也是一个递归过程,一层层递归回去后,得到最终的有序数列。

其中,将两个有序数列合并的算法是最核心的部分:(如a,b存入c,由小到大)

首先,从a,b两个数列的首部开始比较,取小的存入新数列c,不断循环直到任意一个原数列被取完,那么再把另一数列的数据直接存入新数列便能得到合并的结果。

合并两个有序数列的代码是这样实现的的:

//将a,b两个有序数列合并到c数组 
void HB(int a[],int n,int b[],int m,int c[]){     //合并有序数列a,b到c中
	int i, j, k;                                    
	i=j=k=0;
	while (i<n&&j<m){                             //在两个数列里不断择小放入数组c             
		if (a[i]<b[j]) c[k++]=a[i++]; 
		else c[k++]=b[j++]; 
	}                                             //当任意一个数列取完后 
	while(i<n) c[k++]=a[i++];                     //再直接取另一个有序数列的数据
	while (j<m) c[k++]=b[j++];
}

整个过程的实现(排序一个数组):

#include <stdio.h>
#define M 1000000
int a[M],temp[M];
void HB(int a[],int s,int m,int e,int temp[]){       //将a,b两个有序数列合并到c数组 
	int i=s,j=m+1,k=0; 
	while(i<=m&&j<=e){                      //在两个数列里不断择小放入数组c  
		if(a[i]<a[j]) temp[k++]=a[i++];
		else temp[k++]=a[j++];
	}
	while(i<=m) temp[k++]=a[i++];              //任一个原数列取完则直接存储另一个数列的剩余有序数据 
	while(j<=e) temp[k++]=a[j++];
	for (i=0;i<k;i++) a[s+i]=temp[i];          //将新数列存回原数列 
}
void GB(int a[], int s, int e, int temp[]) //递归过程,将大问题拆解成小问题 (s=start,e=end,m=mid)
{
	if (s<e){
		int m=(s+e)/2;                //使一分为二 
		GB(a,s,m,temp);               //使左边有序   
		GB(a,m+1,e,temp);             //使右边有序 
		HB(a,s,m,e,temp);             //合并为有序
	}                            
                              
}
int main()
{
	int n,t,i;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(i=0;i<n;i++) scanf("%d",&a[i]);
		GB(a,0,n-1,temp);
		for(i=0;i<n;i++) printf("%d\n",a[i]);
	}
	return 0;
}

拓展:

由于归并过程会将整体拆成n个小块,那么对小块做 合并有序数列外 的其它操作,就可以间接地解其它问题。

如下题:

题目描述

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。

现在,给你一个N个元素的序列,请你判断出它的逆序数是多少。

比如 1 3 2 的逆序数就是1。

我们可以将所求序列划分为两两相等的子序列,对每个子序列进行操作。如下,(未AC代码,仅参考,日后修正)


#include <stdio.h>                                   //归并排序:从小到大排序一个数组 
#define M 1000000
int cnt,a[M];
void HB(int a[],int s,int m,int e){       //将a,b两个有序数列合并到c数组 
	int i,j;
	for(i=s;i<=m;i++){
		for(j=m+1;j<=e;j++)
			if(a[j]<a[i]) cnt++;          //当后面的值小于前面的值时,即逆序 
	}
}
void GB(int a[], int s, int e) //递归过程,将大问题拆解成小问题 (s=start,e=end,m=mid)
{
	if (s<e){
		int m=(s+e)/2;                //使一分为二 
		GB(a,s,m);               //使左边有序   
		GB(a,m+1,e);             //使右边有序 
		HB(a,s,m,e);             //合并为有序
	}                            
                              
}
int main()
{
	int n,t,i;
	scanf("%d",&t);
	while(t--){
		cnt=0;
		scanf("%d",&n);
		for(i=0;i<n;i++) scanf("%d",&a[i]);
		GB(a,0,n-1);
		printf("%d\n",cnt);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_30007603/article/details/81414272
今日推荐