归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。那么首先,我们了解下分治法:
将一个大的问题分解成为一些较小的子问题,分别求解各个子问题,然后将各个子问题合并就可以得到问题的解。分治法和递归的思想非常类似,分治法一般是划分为若干个相等的子问题,而递归则一般是逐层减一得到最简单的子问题,接着再一层一层返回。
归并排序基本原理
通过对若干个有序结点序列的归并来实现排序,所谓归并是指将若干个已排好序的部分合并成一个有序的部分。
下面这幅图能够直观地表示这个过程:
我们可以看到,对于初始的无序数列,我们要想得到最后的有序数列,它需要不断地分割以得到若干个有序的数列,再将这些有序的数列进行归并处理,如四合二、二合一,直到最终归并为一个有序数列,那么这个数列便是排序好的结果。
而归并排序,在其中的主要工作,便是利用递归,把大的数组一半半剖开,直到它成为有序数列,再进行两个有序数列的归并操作将它们合并为一个有序数列,这也是一个递归过程,一层层递归回去后,得到最终的有序数列。
其中,将两个有序数列合并的算法是最核心的部分:(如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;
}