数据结构——排序(Part4归并排序)

(图源:大话数据结构)

好文分享:排序算法解析:https://blog.csdn.net/kexuanxiu1163/article/details/103051357 

目录

0准备工作

1归并排序

2归并优化


0准备工作

保存排序内容的自定义结构体,其中顺序表的长度,不算哨兵(下标为零的部分)

#define MAX 10
typedef struct SqlistData
{
    int r[MAX+1];//把r[0]空出来当哨兵或者临时变量
    int length;//记录长度,因为0被空出来了,所以长度和下标此时统一了

}SqList;

交换接口

void swap(SqList * L,int i,int j)//交换
{
    int temp = L->r[i];
    L->r[i] = L->r[j];
    L->r[j] = temp;
}

打印接口

void print(SqList * L)
{
    qDebug()<<"开始打印";
    for(int i = 1;i<=L->length;++i)
    {
        qDebug()<<L->r[i];
    }
}

顺序表的储存结构如下:

                           

零位下标什么也不储存,所以无论是遍历还是排序,都是从下标1开始,顺序表长度为5,小标和顺序表中的元素序号完全一致。

 1归并排序

           

                        

  执行代码如下:

void MSort(int SR[],int TR1[],int s,int t)
{
    int m;
    int TR2[MAX+1];
    if(s==t)
        TR1[s] = SR[s];
    else
    {
        m = (s+t)/2;
        MSort(SR,TR2,s,m);
        MSort(SR,TR2,m+1,t);
        Merge(TR2,TR1,s,m,t);
    }
    
}

接口要求的输入值:顺序表表头地址,起始元素下标,终点元素下标 

 典型的递归调用,下面举例说明程序是如何运行的:

                                                                                 

此图没有显示出整理(Merge)的部分,只是展示出了分离(MSort)部分的递归调用规律 

函数关键,在于起点和终点的输入,1,m 第一部分。m+1,End,第二部分,一直调用一直分割,知道最后分割成单个元素,然后返回,进行整理排序。

下面是书中解释:

                                   

MSort的作用:就是把顺序表进行分离

下面看看整理函数Merge

void Merge(int SR[],int TR[],int i,int m,int n)
{
    int j,k,d;
    for(j = m+1,k=i;i<=m&&j<=n;k++)//两个游标,
    {
        if(SR[i]<SR[j])
            TR[k]=SR[i++];
        else
            TR[k]=SR[j++];
        //谁小放谁
    }
    qDebug()<<"k"<<k<<"j"<<j<<"i"<<i<<"m"<<m<<"n"<<n;
    //因为前后分成了两个部分,长度不一样,最后需要补回来
    //
    if(i<=m)//后半部分先结束,前半部分长,有剩余元素
    {
        for(d = 0;d<=m-i;d++)
            TR[k+d]=SR[i+d];
    }
    if(j<=n)//前半部分先结束,后半部分长,有剩余元素
    {
        for(d=0;d<=n-j;d++)
            TR[k+1]=SR[j+1];
    }
}

整理程序再解析:

    for(j = m+1,k=i;i<=m&&j<=n;k++)//两个游标,
    {
        if(SR[i]<SR[j])
            TR[k]=SR[i++];
        else
            TR[k]=SR[j++];
        //谁小放谁
    }

 用下面这张图最好理解:

                     

递归到最后,其实就是两个数值比大小,不断返回的过程中,TR1TR2的长度才不断累积

这一段很好理解,TR1TR2自身都是有顺序的,此时需要的是TR1TR2之间的排序

TR1的第i个,和TR2的第j个比较,谁小谁放到TR里面,自然,TR的序号是要不断累加的

如果TR1的第i个元素比较小,被选中放入了TR中,那么自然i累加,不能同一个位置上的元素使劲儿和之后的比较。

这段程序写的非常简练。

但是因为TR1TR2的长度不一样而且比较也是非常随机的,万一TR1中的最大元素都比TR2中的小,TR1循环结束的时候

TR2还在第一个元素,此时就要停止循环,所以循环条件是TR1的游标和TR2的游标,都要小于末尾元素的序号;

跳出循环后,需要把没有插入的元素,插入到TR的后面,此时没有进入TR的元素一定都是数值较大的元素,直接在后面补上就行。

    if(i<=m)//后半部分先结束,前半部分长,有剩余元素
    {
        for(d = 0;d<=m-i;d++)
            TR[k+d]=SR[i+d];
    }
    if(j<=n)//前半部分先结束,后半部分长,有剩余元素
    {
        for(d=0;d<=n-j;d++)
            TR[k+d]=SR[j+d];
    }

 此时再看这段程序 ,TR1剩余,进入第一段,TR2剩余,进入第二段

之后就是累计然后不断放入的过程了。

如果你仔细观察代码,看内存空间的使用,就会发现,分割其实什么都没有做,分割到最后,每个元素的编号和原来该元素在顺序表中的编号是一样的,分割,完全是为了后期的重新排序,排序才是真的关键。

这就是TR2,在MSort中存在的意义,相当于temp,临时储存原始变量。

     

2归并优化

              

                    

                    

          

              

                     

                  

发布了85 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41605114/article/details/104829651