您的位置:首页 > 理论基础 > 数据结构算法

归并排序

2017-12-15 21:31 99 查看
       归并就是将两个或多个有序表合并成一个有序表的过程。若将两个有序表合并成一个有序表则称为二路归并,同理,有三路归并,四路归并等。二路归并最为简单和常用,即适用于内排序,也适用于外排序,这里只讨论二路归并。例如,有两个有序表(7,12,15,20)和(4,8,10,17),进行二路归并后得到的有序表为(4,7,8,10,12,15,17,20)。以后若没有特别说明,所述的归并均指二路归并。

       二路归并算法很简单,假定待归并的两个有序表分别存于数组a中从下标s到下标m的单元和从下标m+1到下标t的单元(s<=m,m+1<=t),结果有序表存于数组r中从下标s到下标t的单元,并令i,j,k分别指向这些有序表的第一个单元。归并过程为:比较a[i].stn和a[j].stn的大小,若a[i].stn<=a[i].stn,则将第一个有序表中的元素a[i]复制到a[k]中,并令i和k分别增1,使之分别指向后一元素位置,否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别增1;如此循环下去,直到其中的一个有序表比较和复制完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。

        二路归并算法描述为:

//二路归并算法描述
public static void twoMerge(Object []a ,Object []r ,int s, int m, int t)
{
//相邻有序表a[s]~a[m]和a[m+1]~a[t]归并为一个有序表r[s]~r[t]
int i,j,k;
//分别给指示每个有序表元素位置的指针赋初值
i=s;j=m+1;k=s;
//两个有序表中同时存在未归并元素时的处理过程
while(i<=m && j<=j)
{
if(((Comparable)a[i]).compareTo(a[j])<0)
{
r[k]=a[i];
i++;
k++;
}
else
{
r[k]=a[j];
j++;
k++;
}
}
//对第一个有序表中可能存在的未归并元素进行处理
while(i<=m)
{
r[k]=a[i];
i++;
k++;
}
//对第二个有序表中可能存在的未归并元素进行处理
while(j<=t)
{
r[k]=a[j];
j++;
k++;
}
}


        归并排序就是利用归并操作把一个无序表排列成一个有序表的过程。若利用二路归并操作则称为二路归并排序。二路归并排序的过程是,首先把待排序区间(即无序表)中的每个元素都看作一个有序表,则n个元素构成n个有序表,接着两两归并,即第一个表同第2个表归并,第3个表同第4个表归并,......,若最后只剩下一个表,则直接进入下一趟归并,这样就得到了n/2(向上取整)个长度为2(最后一个表的长度可能小于2)的有序表,称此为一趟归并;然后再两两有序表归并,得到(n/2(向上取整))/2(向上取整)个长度为4(最后一个表的长度可能小于4)的有序表;如此进行下去,直到归并log2n(向上取整)趟后得到一个长度为n的有序表为止。

       要给出二路归并的排序算法,首先要给出一趟归并排序的算法。设数组a
中每个有序表的长度为len(但最后一个表的长度可能小于len),进行两两归并后的结果存于数组r
中。进行一趟归并排序时,对于a中可能除最后一个(当a中有序表个数为奇数时)或两个(当a中有序表个数为偶数,但最后一个表的长度小于len时)有序表,共剩有偶数个长度为len的有序表,由前到后对每两个假定从下标p开始的有序表调用twoMerge(a,r,p,p+len-1,p+2*len-1)过程即可完成归并;对可能剩下的最后两个有序表(后一个长度小于len,否则不会剩下),假定是从下标p开始的,则调用twoMerge(a,r,p+len-1,n-1)过程即可完成归并;对可能剩下的最后一个有序表(其长度小于等于len),则把它直接复制到r中对应区间即可。至此,一趟归并完成。

       进行一趟二路归并的算法描述为:
//进行一趟二路归并的算法描述
public static void mergePass(Object []a, Object []r , int n, int len)
{
//把数组a
中每个长度为len的有序表两两归并到数组r

int p=0; //p为每一对待合并表的第一个元素的下标,初值为-
while(p+2*len-1<=n-1) //两两归并长度均为len的有序表
{
twoMerge(a,r,p,p+len-1,p+2*len-1);
p+=2*len;
}
if(p+len-1<n-1) //归并最后两个长度不等的有序表
{
twoMerge(a,r,p,p+len-1,n-1);
}
else //把剩下的最后一个有序表复制到r中
{
for(int i=p;i<=n-1;i++)
{
r[i]=a[i];
}
}
}

       二路归并排序的过程需要进行log2n(向上取整)趟,第1趟len等于1,以后每进行一趟将len加倍。假定待排序的n个记录保存在数组a
中,归并过程中使用的辅助数组为r
,第1趟由a归并到r,第2趟由r归并到a;如此反复进行,直到n个记录成为一个有序表为止。

      在归并过程中,为了将最后的排序结果仍置于数组a中,需要进行的趟数为偶数;如果实际只需奇数趟(即log2n(向上取整)的值为奇数)完成,那么最后还要进行一趟,正好此时r中的n个有序元素为一个长度不大于len(此时len>=n)的表,将会被直接复制到a中。

       二路归并排序的算法描述为:

//二路归并排序的算法描述
public static void mergeSort(Object []a, int n)
{
//采用归并排序的方法对数组a中的n个记录进行排序
Object []r=new Object
; //定义长度为n的辅助数组r
int len=1;
while(len<n)
{
//从a归并到r中,得到每个有序表的长度为2*len
mergePass(a,r,n,len);
//修改len的值为r中的每个有序表的长度
len*=2;
//从r归并到a中,得到每个有序表的长度为2*len
mergePass(r,a,n,len);
//修改len的值为a中的每个有序表的长度
len*=2;
}
}
       二路归并排序的时间复杂度等于归并趟数与每一趟时间复杂度的乘积。归并趟数为log2n(向上取整)(log2n(向上取整)+1)。因为每一趟归并就是将两两有序表归并,而每一对有序表归并时,记录的比较次数均小于等于记录的移动次数(即由一个数组复制到另一个数组中的记录个数),而记录的移动次数等于这一对有序表的长度之和,所以每一趟归并的移动次数均等于数组中记录的个数n,即每一趟归并的时间复杂度为O(n),因此,二路归并排序的时间复杂度为O(nlog2n)。

       二路归并排序时需要利用同待排序数组一样大小的一个辅助数组,所以其空间复杂度为O(n)。显然它高于任何简单排序算法空间复杂度,同时也高于上面介绍的堆排序和快速排序算法的空间复杂度。

       二路归并排序是稳定的,因为在每两个有序表归并时,若分别在两个有序表中出现有相同排序码的元素,twoMerge算法能够使前一有序表中同一排序码的元素先被复制,后一有序表中同一排序码的元素后被复制,从而确保他们的相对次序不会改变。

      
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息