2.3合并排序的递归、非递归算法,自然合并排序

递归算法

将待排元素分成大小大致相同的两个子集合,分别对这两个集合进行排序,最终将排好序的子集合合并。

《2.3合并排序的递归、非递归算法,自然合并排序》

#include<iostream>  
#include<cstdio>  
  
void Merge(int s[],int t[],int b,int m,int e){//将s[b...m]与s[m+1...e]合并  
    int i=b;//i->前一个块  
    int j=m+1;//j->后一个块  
    int k=0;//k->t数组  
    while(i<=m&&j<=e){  
        if(s[i]<=s[j])t[k++]=s[i++];  
        else t[k++]=s[j++];  
    }  
    while(i<=m){  
        t[k++]=s[i++];  
    }  
    while(j<=e){  
        t[k++]=s[j++];  
    }  
    for(int i=0;i<k;i++){  //每次都把序数组t的值赋回给s,这样s对于区间[b,e]也是有序的
        s[b+i]=t[i];  
    }  
}  
  
  
void MSort(int s[],int t[],int b,int e){  
    if(b<e){  
        int m;  
        m=(b+e)/2;  
        MSort(s,t,b,m);  
        MSort(s,t,m+1,e);  
        Merge(s,t,b,m,e);  
    }  
}  
  
  
int main(){  
    int a[10]={2,5,1,3,4,9,7,8,6,0};  
    int temp[10];  
    MSort(a,temp,0,9);  
    for(int i=0;i<10;i++){  
        printf("%d ",a[i]);  
    }  
    printf("\n");  
} 

非递归算法

有三个函数:

(1)Merge函数,用于合并两个有序子段。这个函数主要是用于将a数组中[l,m]区间和[m+1,r]区间的数有序的合并b数组的[l,r]区间。这里不同于递归方法里的Merge函数,b数组从l开始到r结束是有序的并且不用在Merge函数尾部将b数组的有序部分赋值给a数组(原因下面提到)。

(2)MergePass函数,这个函数的功能对于长为n数组进行归并,两个两个地合并长度为s的子段,当然我们的数组长度不可能每次正好满足“以s为子段长度时正好能两两合并,一个不多一个不少”。所以我们要对特殊情况进行判断:

 while(i<=n-2*s){//剩下不超过2s长度的元素
        Merge(x,y,i,i+s-1,i+2*s-1);
        i=i+2*s;
 }

在剩下的元素不能组成两个子段的时候分情况讨论:

a.当剩下元素可以凑成一个长为s子段,Merge(x,y,i,i+s-1,n-1);

b.当剩下元素连一个长为s的字段都凑不成时,我们直接将其略过(不排序)

(3)MergeSort函数,这个数组首先设一个临时数组b,与要归并的子段长度s,s初始为1,在s<n的前提下,我们每次调用MergePass函数,使得a数组中,以s为子段长度,每个子段内部都有序地排列,这次排好的序列体现在b数组上,a数组此时没变!然后将s+=s;可以开始两两合并之前长为s的子段啦!这次把b再合并到a上,所以一趟下来,最后a是“有序的”,这也是Merge函数里不用把数组b再赋给a的原因了,省了一趟赋值!

#include<iostream>
#include<cstdio>
#include<string>
using namespace std;

void Merge(int a[],int b[],int l,int m,int r){
    int i=l,j=m+1,k=l;
    while(i<=m&&j<=r){
        if(a[i]<a[j])b[k++]=a[i++];
        else b[k++]=a[j++];
    }
    while(i<=m)b[k++]=a[i++];
    while(j<=r)b[k++]=a[j++];
}
void MergePass(int x[],int y[],int s,int n){
    int i=0;
    while(i<=n-2*s){//剩下超过2*s长度的元素
        Merge(x,y,i,i+s-1,i+2*s-1);
        i=i+2*s;
    }
    //剩下不超过2s长度的元素
    if(i+s<n)Merge(x,y,i,i+s-1,n-1);//剩下的长度小于2*s大于s
    else{//剩下的长度小于s
        for(int j=i;j<=n-1;j++){
            y[j]=x[j];
        }
    }
}
void MergeSort(int a[],int n){
    int b[10];
    int s=1;
    while(s<n){
        MergePass(a,b,s,n);//把a合并到b中
        s+=s;
        MergePass(b,a,s,n);//把b合并到a中
        s+=s;
    }
}


int main(){
    int a[10]={2,5,1,3,4,9,7,8,6,0};
    int n=10;
    MergeSort(a,n);
    for(int i=0;i<n;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
    return 0;
}

自然合并排序

自然合并排序:对于初始给定的数组,通常存在多个长度大于1的已自然排好序的子数组段.例如,若数组a中元素为{4,8,3,7,1,5,6,2},则自然排好序的子数组段有{4,8},{3,7},{1,5,6},{2}.用一次对数组a的线性扫描就足以找出所有这些排好序的子数组段.然后将相邻的排好序的子数组段两两合并,构成更大的排好序的子数组段({3,4,7,8},{1,2,5,6}).继续合并相邻排好序的子数组段,直至整个数组已排好序.

所以我们可以设变量flag表示序列中有几段已经排好序的子段。

设数组t[flag]表示每个有序子段的第一个元素所在的位置。

代码如下:

#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
int t[10],flag=0;

void Merge(int a[],int b[],int l,int m,int r){
    int i=l,j=m+1,k=l;
    while(i<=m&&j<=r){
        if(a[i]<a[j])b[k++]=a[i++];
        else b[k++]=a[j++];
    }
    while(i<=m)b[k++]=a[i++];
    while(j<=r)b[k++]=a[j++];
}

void getPos(int a[],int t[],int n){
    t[flag++]=0;
    for(int i=0;i<n-1;i++){
        if(a[i+1]<a[i])t[flag++]=i+1;
    }
    for(int i=0;i<flag;i++)printf("%d  ",t[i]);
    printf("\n");
}

void MergePass(int x[],int y[],int s,int n){
    int i=0;//i表示第几段
    while(i<=flag-2*s){
        int r=((i+2*s)<flag)?t[i+2*s]:n;
        Merge(x,y,t[i],t[i+s]-1,r-1);//把两个长度为s的段合并在一起
        i=i+2*s;
    }
    if(i+s<flag)Merge(x,y,t[i],t[i+s]-1,n-1);
    else{
        for(int j=t[i];j<=n-1;j++){
            y[j]=x[j];
        }
    }
}
void MergeSort(int a[],int n){
    int b[10];
    int s=1;
    while(s<flag){//最多flag段
        MergePass(a,b,s,n);//把a合并到b中
        s+=s;
        MergePass(b,a,s,n);//把b合并到a中
        s+=s;
    }
}

int main(){
    int a[10]={2,1,5,3,4,9,7,8,0,6};
    int n=10;
    getPos(a,t,n);
    MergeSort(a,n);
    for(int i=0;i<n;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
    return 0;
}

《2.3合并排序的递归、非递归算法,自然合并排序》

(第一行输出的是t数组,标记每个有序段的起始位置

第二行输出的是排好序的数组)

    原文作者:递归算法
    原文地址: https://blog.csdn.net/m0_37579232/article/details/80090161
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞