1. 程式人生 > >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;
}

(第一行輸出的是t陣列,標記每個有序段的起始位置

第二行輸出的是排好序的陣列)