1. 程式人生 > >演算法 -- 歸併排序之自然排序

演算法 -- 歸併排序之自然排序

定義:
歸併排序是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法的一個典型應用.應該將已經有序的子序列合併,得到完全有序的序列:即先使每個子序列有序,再使子序列段間有序.若將兩個有序表合併成一個有序表,稱為二路歸併. [摘錄於維基百科]

無論哪一種歸併演算法的實現,它都需要用到一個MergeArray函式實現兩個有序陣列的合併.如下:
/**
 * 首先定義一個新陣列b儲存合併後的資料資訊,具體的合併過程即架迴圈遍歷
 * 兩個陣列,如果前一部分陣列元素小於後一部分陣列元素則i++(前陣列的迴圈
 * 變數),否則j++(後陣列的迴圈變數).當有一個數組遍歷結束時,跳出迴圈.
 * 跳出之後,如果另一個數組沒有遍歷結束,則將其剩餘元素儲存在p陣列中,
 * 否則跳過.
 **/
void MergeArray ( int a[] , int left , int mid , int right ) { // 迴圈變數i,j分別為兩部分陣列的檢測指標 int i = left , j = mid + 1 , k = 0 , m , p ; int b[N] ; // 其中任意一個數組訪問結束,則迴圈結束 while ( i <= mid && j <=right ) { if ( a[i] <= a[j] ) { // 前一個數組當前元素不大於後一個數組,則i++ b[k++] = a[i++] ; } else
{ // 否則j++ b[k++] = a[j++] ; } } // 前一個數組沒有遍歷結束則繼續迴圈,使其儲存在b陣列中 while ( i <= mid ) { b[k++] = a[i++] ; } // 後一個數組沒有遍歷結束則繼續迴圈,使其儲存在b陣列中 while ( j <= right ) { b[k++] = a[j++] ; } }

因為遞迴式的歸併排序實現比較簡單,在這裡我就不再介紹. 因此下面主要分享非遞迴方法的實現,分別是:一般排序和自然排序.

A. 非遞迴歸併排序の一般排序: 
        從小的部分實現,首先是一個元素為一組,每次以2的倍數增長,每一次分組後兩個小組為一組進行合併即呼叫MergeArray合併函式實現合併,其實就是"從小到大"的思想,與遞迴的思想剛好相反.(其中有的細節可以參考程式碼,重點是理解程式碼中的註釋)
/*************************************************************************
 **     >  Name : FDG_Combine.c
 **     > Author: LiYingXiao (Sweethreart502) 
 **     >  Mail : [email protected]
 **     >  Blog : http://blog.csdn.net/u013166575
 **     > Created Time: 2015年10月7日 星期一 20時34分15秒
 ************************************************************************/
#include <iostream>

#define Max 5

// 數組合並演算法
void MergeArray ( int * a , int left , int middle , int right , int n ) 
{
    int * p = new int[n] ;

    int i = left , j = middle + 1 , k = 0 ;

    // 架迴圈使得兩個陣列按照大小順序儲存在p陣列中
    while ( i <= middle && j <= right ) {
        if ( a[i] < a[j] ) {
            p[k++] = a[i++] ;
        } else {
            p[k++] = a[j++] ;
        }
    }

    while ( i <= middle ) {
        p[k++] = a[i++] ;
    }

    while ( j <= right ) {
        p[k++] = a[j++] ;
    }

    // 將合併後的資訊賦值給原陣列
    for ( i = 0 ; i < n ; i++ ) {
        a[left+i] = p[i] ;
    }

    delete [] p ;
}

// 歸併排序演算法
void MergeSort ( int * a , int n )
{
    int length = 1 ;
    int begin ;

    for ( length = 1 ; length < n ; length *= 2 ) {
        //  每一次的分組劃分合併處理,都是從下標0開始的
        begin = 0 ;
        while ( ( begin + 2 * length ) < n ) {
            // 呼叫合併函式,實現兩兩小數組合並
            MergeArray ( a , begin , ( 2*begin + 2*length -1 ) / 2 , begin + 2*length - 1 , 2*length ) ;
            // 迴圈到下一對合並的小陣列
            begin += 2*length ;
        } 

        // 如果剩下的長度不足夠2*length但begin+length<n,則繼續將其合併
        if ( ( begin + length ) < n ) {
            MergeArray ( a , begin , begin + length - 1 , n - 1 , n ) ;
        }

    }
}

// 主函式
int main (  ) 
{
    int array[Max] = { 0 } ;

    std::cout << "Please input the " << Max << " elements of the array : " << std::endl ;
    for ( int i = 0 ; i < Max ; i++ ) {
        std::cin >> array[i] ;
    }

    // 呼叫歸併排序函式
    MergeSort ( array , Max ) ;

    // 排序結束後的陣列進行輸出展示
    std::cout << std::endl << "The sorted array is : " << std::endl ;
    for ( int i = 0 ; i < Max ; i++ ) {
        std::cout << array[i] << " " ;
    }

    std::cout << std::endl ;

    return 0 ;
}
B. 非遞迴歸併排序の自然歸併:    
     自然合併的核心主要是一個Pass函式,這個函式中設定了一個array陣列,來存放每一組有序元素的起始元素的下標,最後再將最後一個元素的下標+1存放為array陣列的最後一個元素,這樣,在後面的合併實現中會顯現出這樣記錄的原因.(其中具體實現可以參考程式碼,重點是理解程式碼中的註釋)
/*************************************************************************
 **     >  Name : Nature_Combine.c
 **     > Author: LiYingXiao (Sweethreart502) 
 **     >  Mail : [email protected]
 **     >  Blog : http://blog.csdn.net/u013166575
 **     > Created Time: 2015年10月7日 星期一 20時34分15秒
 ************************************************************************/
#include <iostream>
#define N 10

// 定義全域性陣列,記錄每個子陣列的其實座標
int array[N] ;

// 兩個數組合並函式
void MergeArray ( int a[] , int left , int right , int mid )
{
    // 迴圈變數i,j分別為兩部分陣列的檢測指標
    int i = left , j = mid + 1 , k = 0 , m , p ; 

    int b[N] ;

    while ( i <= mid && j <=right ) {
        if ( a[i] <= a[j] ) {
            b[k++] = a[i++] ;
        } else {
            b[k++] = a[j++] ;
        }
    }

    while ( i <= mid ) {
        b[k++] = a[i++] ;
    }

    while ( j <= right ) {
        b[k++] = a[j++] ;
    }

    for ( p = 0 , m = left ; m <= right ; p++ , m++ ) {
        a[m] = b[p] ;
    }
}

// 掃描演算法(這個函式是理解的關鍵!!!)
int Pass ( int a[] , int n )  
{
    int num = 0 , i ;
    int biger = a[0] ;
    array[num++] = 0 ;  // 將全域性陣列下標為0的值記為0

    for ( i = 1 ; i < n ; i++ ) {
        if ( a[i] >= biger ) {
            biger = a[i] ;
        } else {
            array[num++] = i ;
            biger = a[i] ;
        }
    }

    // array陣列儲存每一組有序的陣列的起始元素的下標,同時儲存最後一個元素的下標+1,為了方便最後一組元素的合併二需要!!!
    array[num++] = N ;

    return num ;        // num此時為陣列最後一個元素下標+1
}

// 歸併排序的自然實現
void MergeSort ( int a[] , int n , int left , int right ) 
{
    int i ; 

    int num = Pass ( a , n ) ;

    while ( num != 2 ) {
        // num = 2 說明已經排序完成即只儲存了下標為1的元素
        // 每迴圈一次,執行一次pass函式

        for ( i = 0 ; i < num ; i += 2 ) {
        // array[i]即合併資料的起點; array[i+2]後一組的後面一組的起點-1即合併資料的終點; array[i+1]後一組的起點-1即合併資料的中點
            MergeArray ( a , array[i] , array[i+2] - 1 , array[i+1] - 1 ) ;

//            num = Pass ( a, n ) ;
        }
    num = Pass ( a , n ) ;
    }
}

// 主函式
int main (  )
{
    int a[N] ;
    int i ;

    std::cout << "Printf input the num :" << std::endl ;
    for ( i = 0 ; i < N ; i++ ) {
        std::cin >> a[i] ;
    }

    MergeSort ( a , N , 0 ,N - 1 ) ;

    std::cout << "Output the num :" << std::endl ;
    for ( i = 0 ; i < N ; i++ ) {
        std::cout << a[i] << " " ;
    }

    std::cout << std::endl ;

    return 0 ;
}
 以上就是我今天所要總結的歸併排序演算法的實現.歡迎大家互相討論,提出意見.