归并排序

 归并排序算法
        “归并”一词的中文含义就是合并、并入的意思,而在数据结构中的定义是将两个或两个以上的有序表组合成一个新的有序表。
        归并排序(Merging Sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。 
        好了,有了对归并排序的初步认识后,我们来看代码。

/*  对顺序表L作归并排序  */
void  MergeSort(SqList  * L)

  MSort(L
-> r,L -> r, 1 ,L -> length);
}

 

        一句代码,别奇怪,它只是调用了另一个函数而已。为了与前面的排序算法统一,我们用了同样的参数定义SqList *L,由于我们要讲解的归并排序实现需要用到递归调用 ,因此我们外封装了一个函数。假设现在要对数组{50,10,90,30,70,40,80,60,20}进行排序,L.length=9,我现来看看MSort的实现。

复制代码
/*  将SR[s..t]归并排序为TR1[s..t]  */
1   void  MSort( int  SR[], int  TR1[], int  s,  int  t)
2  {
3    int  m;
4    int  TR2[MAXSIZE + 1 ];
5    if (s == t)
6    TR1[s] = SR[s];
7    else
8   {
9    m = (s + t) / 2 ;    /*  将SR[s..t]平分为SR[s..m]和SR[m+1..t]  */
10    MSort(SR,TR2,s,m);  /*  递归地将SR[s..m]归并为有序的TR2[s..m]  */
11    MSort(SR,TR2,m + 1 ,t);  /*  递归地将SR[m+1..t]归并为有序TR2[m+1..t]  */
12    Merge(TR2,TR1,s,m,t);  /*  将TR2[s..m]和TR2[m+1..t]归并到TR1[s..t]  */
13   }
14  }
复制代码

 

1) MSort被调用时,SR与TR1都是{50,10,90,30,70,40,80,60,20},s=1,t=9,最终我们的目的就是要将TR1中的数组排好顺序。
2) 第5行,显然s不等于t,执行第8~13行语句块。
3) 第9行,m=(1+9)/2=5。m就是序列的正中间下标
4) 此时第10行,调用“MSort(SR,TR2,1,5);”的目标就是将数组SR中的第1~5的关键字归并到有序的TR2(调用前TR2为空数组),第11行,调用“MSort(SR,TR2,6,9);”的目标就是将数组SR中的第6~9的关键字归并到有序的TR2。也就是说,在调用这两句代码之前,代码已经准备将数组分成了两组了。如图9-8-2。
 


5) 第12行,函数Merge的代码细节一会再讲,调用“Merge(TR2,TR1,1,5,9);”的目标其实就是将第10和11行代码获得的数组TR2(注意它是下标为1~5和6~9的关键字分别有序)归并为TR1,此时相当于整个排序就已经完成了。如图9-8-3。
 
6) 再来看第10行递归调用进去后,s=1,t=5,m=(1+5)/2=3。此时相当于将5个记录再为三个和两个。继续递归进去,直到细分为一个记录填入TR2,此时s与t相等,递归返回,如图9-8-4的左图。每次递归返回后都会执行当前递归函数的第12行,将TR2归并到TR1中。如图9-8-4的右图。最终使得当前序列有序。
 

7) 同样的第11行也是类似方式,如图9-8-5。
 
8) 此时也就是刚才所讲的最后一次执行第12行代码,将{10,30,50,70,90}与{20,40,60,80}归并为最终有序的序列。
可以说,如果对递归函数的运行方式理解比较透的话,MSort函数还是很好理解的。我们来看看整个数据变换示意图,如图9-8-6。
 

现在我们来看看Merge函数的代码是如何实现的。

 

复制代码
/*  将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n]  */
1   void  Merge( int  SR[], int  TR[], int  i, int  m, int  n)
2  {
3    int  j,k,l;
4    for (j = m + 1 ,k = i;i <= &&  j <= n;k ++ /*  将SR中记录由小到大归并入TR  */
5   {
6     if  (SR[i] < SR[j])
7     TR[k] = SR[i ++ ];
8     else
9     TR[k] = SR[j ++ ];
10   }
11    if (i <= m)
12   {
13     for (l = 0 ;l <= m - i;l ++ )
14     TR[k + l] = SR[i + l];   /*  将剩余的SR[i..m]复制到TR  */
15   }
16    if (j <= n)
17   {
18     for (l = 0 ;l <= n - j;l ++ )
19     TR[k + l] = SR[j + l];   /*  将剩余的SR[j..n]复制到TR  */
20   }
21  }
复制代码

 

1) 假设我们此时调用的Merge就是将{10,30,50,70,90}与{20,40,60,80}归并为最终有序的序列,因此数组SR为{10,30,50,70,90,20,40,60,80},i=1,m=5,n=9。
2) 第4行,for循环,j由m+1=6开始到9,i由1开始到5,k由1开始每次加1,k值用于目标数组TR的下标。
3) 第6行,SR[i]=SR[1]=10,SR[j]= SR[6]=20,SR[i]<SR[j],执行第7行,TR[k]=TR[1]=10,并且i++。如图9-8-7。
 


4) 再次循环,k++得到k=2,SR[i]=SR[2]=30,SR[j]= SR[6]=20,SR[i]>SR[j],执行第9行,TR[k]=TR[2]=20,并且j++,如图9-8-8。
 

5) 再次循环,k++得到k=3,SR[i]=SR[2]=30,SR[j]= SR[7]=40,SR[i]<SR[j],执行第7行,TR[k]=TR[3]=30,并且i++,如图9-8-9。
 

6) 接下来完全相同的操作,一直到j++后,j=10,大于9退出循环。如图9-8-10。
 

7) 第11~20行的代码,其实就将归并剩下的数组数据,移动到TR的后面。当前k=9,i=m=5,执行第13~20行代码,for循环l=0,TR[k+l]=SR[i+l]=90。大功告成。




代码的详解过程:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>

void PrintArr(int *pnArr, int nLen)
{
    for (int i = 0; i < nLen; i++)
    {
        printf("%d ", pnArr[i]);
    }
    printf("\n");
}
//合并两个数组
void Merge(int data[], int nLpos, int nRpos, int nRightEnd)
{
    int i;
    int k = nLpos;
    int nLeftEnd = nRpos;
    int nTmpPos = 0;
    int nLen = nRightEnd - nLpos + 1;
    int *pnArr = (int *)malloc(sizeof(int) * nLen);
    ++nRpos;
    while (nLpos <= nLeftEnd && nRpos <= nRightEnd)
    {
        if (data[nLpos] <= data[nRpos])
        {
            pnArr[nTmpPos++] = data[nLpos++];
        }
        else
        {
            pnArr[nTmpPos++] = data[nRpos++];
        }
    }
    while (nLpos <= nLeftEnd)
    {
        pnArr[nTmpPos++] = data[nLpos++];
    }
    while (nRpos <= nRightEnd)
    {
        pnArr[nTmpPos++] = data[nRpos++];
    }
    
    nTmpPos = 0;
    for (i = k; i <= nRightEnd; i++)
    {
        data[i] = pnArr[nTmpPos++];
    }

    free(pnArr);
}
void MergeSort(int *pnArr, int nLeft, int nRight)
{

    if (nRight > nLeft)
    {
        //1分解
        int nMid = (nLeft + nRight) / 2;
        
        //2解决      将一个序列分成两个部分,左右两部分,知道不能分为止,这样吧各个部分存储到栈里面,
		//左右都不可以分的时候,调用3合并的函数,然后按出栈的顺序,各个调用栈中后面的函数
		//会产生很多的左右分支,然后按着倒序,左右分支合并
        MergeSort(pnArr, nLeft, nMid);
        MergeSort(pnArr, nMid + 1, nRight);

        //3合并
        Merge(pnArr, nLeft, nMid, nRight);
    }
}
int main()
{
    srand(time(NULL));
    int nArr[10];
    for (int i = 0; i < 10; i++)
    {
        nArr[i] = rand()%100;
    }
    //产生数据
    printf("排序前:");
    PrintArr(nArr, 10);
    
    MergeSort(nArr, 0, 9);
    printf("排序后:");
    PrintArr(nArr, 10);

	system("pause");
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值