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

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

無論哪一種歸併算法的實現,它都需要用到一個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 ;
}
 以上就是我今天所要總結的歸併排序算法的實現.歡迎大家互相討論,提出意見.
發佈了30 篇原創文章 · 獲贊 20 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章