歸并排序

歸并排序

所謂歸并,就是將兩個或兩個以上的有序表合并成一個新的有序表。如下圖所示,有兩個已經排好序的有序表A[1]~A[n]和B[1]~B[m](在圖中只給出了它們的關鍵字),通過歸并把它們合成一個有序表C[1]~C[m+n]。

基本思想:歸并(Merge)排序法是將兩個(或兩個以上)有序表合并成一個新的有序表,即把待排序序列分為若干個子序列,每個子序列是有序的。然后再把有序子序列合并為整體有序序列。

歸并排序
歸并排序

兩路歸并算法的C++描述

歸并排序
歸并排序

在這個算法中,兩個待歸并的有序表首尾相接存放在數組sourceTable.Arr[]中,其中第一個表的下標范圍是從 left 到 mid,另一個表的下標范圍從 mid+1 到 right。歸并后得到的新有序表存放在另一個輔助數組中 mergedTable.Arr[] 中,其下標范圍從 left 到 right。

template <class Type>
void merge ( sortlist<Type> & sourceTable, sortlist <Type> & mergedTable,
 const int left, const int mid,  const int right ) {
    int i = left,  j = mid+1,  k =left;//指針初始化
    while ( i <= mid && j <= right ){
        if ( sourceTable.Arr[i].getKey() <= sourceTable.Arr[j].getKey()){ 
            mergedTable.Arr[k] = sourceTable.Arr[i];
            i++;  
            k++; 
        } else { 
            mergedTable.Arr[k] = sourceTable.Arr[j]; 
            j++;  
            k++; 
        }
    }
    if ( i <= mid ){
        for ( int p = k, q = i;  q <= mid;  p++, q++ ){
            mergedTable.Arr[p] = sourceTable.Arr[q]; 
        }
    } else {
        for ( int p = k, q = j; q <= right; p++, q++){
            mergedTable.Arr[q] = sourceTable.Arr[p];
        }
    }
}

兩路歸并排序

兩路歸并排序就是利用兩路歸并算法進行排序。

其算法基本思想是:假設初始排序表有n個數據元素,首先把它看成是長度為1的首尾相接的n個有序子表(以后稱它們為歸并項),先做兩兩歸并,得 ?n/2? 個長度為2的歸并項(如果n為奇數,則最后一個歸并項的長度為1);再做兩兩歸并,……,如此重復,最后得到一個長度為n的有序序列。

歸并排序
歸并排序

一趟歸并的算法描述如下

template <class Type> 
void MergePass ( sortlist<Type> & sourceTable,
               sortlist<Type> & mergedTable,  const int len ) {
    int i =0;
    while ( i+2*len <= CurrentSize-1 ){
        merge ( sourceTable, mergedTable, i, i+len-1, i+2*len-1);
        i += 2 * len; 
    }
    if ( i+len <= CurrentSize-1 ){
        merge ( sourceTable, mergedTable,i, i+len-1, CurrentSize-1 );
    }else{
        for ( int j=i; j <= CurrentSize-1; j++ ){
            mergedTable.Arr[j] = sorceTable.Arr[j];
        }
    }        
}

兩路歸并排序算法描述如下

template <class Type> 
void MergeSort ( sortlist<Type> & table ) {
    sortlist<Type> & tempTable;
    int len = 1;
    while ( len < table.CurrentSize ) {
         MergePass (table , tempTable, len );   len *= 2;
         MergePass (tempTable , list , len );   len *= 2;
    }
    delete []tempTable;
}

在兩路歸并排序算法中,函數MergePass( )做一趟歸并,要調用merge( )函數 ?n/(2*len)? ≈ O(n/len)次,而每次merge( )要執行比較次數不超過2*len-1,為O(len),函數MergeSort( )調用MergePass( )正好 ?log2n? 次,所以兩路歸并排序算法總的時間復雜度為O(nlog?n)。

兩路歸并排序占用附加存儲較多,需要另外一個與原待排序數據元素數組同樣大小的輔助數組,所以其空間復雜度為O(n)。 兩路歸并排序是一個穩定的排序方法。

遞歸的歸并排序

在遞歸的歸并排序方法中,首先要把整個排序表劃分為長度大致相等的左右兩個部分,分別稱之為左子表和右子表,對這兩個子表分別進行歸并排序(遞歸),然后再把已排好序的這兩個子表進行兩路歸并,得到一個有序表。

遞歸的歸并排序示例

歸并排序
歸并排序

在遞歸的歸并排序過程中,如果使用前面給出的兩路歸并算法,需要進行數組元素的傳遞,這非常影響歸并的效率。如果排序表采用鏈表的存儲表示,可以得到一種有效的歸并排序算法。

在此排序表以靜態鏈表存儲,設待排序的數據元素存放在類型為的靜態鏈表table中。table.Arr[0]用于表示結果鏈表的頭結點。函數linkListMerge()是將兩個有序的單鏈表歸并成一個有序單鏈表的算法。

靜態鏈表上的遞歸歸并排序示例

歸并排序
歸并排序

在靜態鏈表上實現兩路歸并的算法描述如下

template <class Type> 
int linkListMerge (sortlinklist<Type> &table, const int p, const int q ) {
   int k = 0, i = p, j = q;
   //初始化指針,其中k為結果鏈表的尾結點指針,i、j為搜索指針
   while ( i !=-1 && j !=-1 ){
        if ( table.Arr[i].getKey()<=table.Arr[j].getKey() ){ 
            table.Arr[k].setLink(i);
            k = i; 
            i = table.Arr[i].getLink( ); 
        }else  { 
            table.Arr[k].setLink(j);
            k = j; 
            j = table.Arr[j].getLink( ); 
        }
   }      
   if (!i){
        table.Arr[k].setlink(j); 
   }else{
        table.Arr[k].setLink(i);
   }
   return table.Arr[0].getLink();
}

遞歸歸并排序算法

template <class Type>
int linkListMergeSort (sortlinklist<Type> &table, const int low, const int high )
{
    if ( low >= high ) return low;
    int mid = ( low + high ) / 2;
    return linkListMerge (table, linkListMergeSort ( table, low, mid ),
                          linkListMergeSort ( table,mid+1, right ) );
}

在靜態鏈表上實現的遞歸歸并排序算法,通過鏈接指針的修改以實現數據元素接點的邏輯有序鏈接,因此不需要數據元素移動。計算時間可以用數據元素關鍵字的比較次數來測量。

算法的遞歸深度為O(log?n),所以總的時間復雜度為0(nlog?n)。它也是一種穩定的排序方法。

歸并排序的C++實現

設兩個有序的子文件(相當于輸入堆)放在同一向量中相鄰的位置上:R[low...m],R[m+1...high],先將它們合并到一個局部的暫存向量R1(相當于輸出堆)中,待合并完成后將R1復制回R[low...high]中。

合并過程:

  1. 設置i,j和p三個指針,其初值分別指向這三個記錄區的起始位置;
  2. 合并時依次比較R[i]和R[j]的關鍵字,取關鍵字較小的記錄復制到R1[p]中;
  3. 然后將被復制記錄的指針i或j加1,以及指向復制位置的指針p加1。
  4. 重復這一過程直至兩個輸入的子文件有一個已全部復制完畢(不妨稱其為空),此時將另一非空的子文件中剩余記錄依次復制到R1中即可。
歸并排序
歸并排序
歸并排序
歸并排序
void Merge(int src[], int des[], int low, int mid, int high)
{
    int i = low;
    int j = mid + 1;
    int k = low;

    while( (i <= mid) && (j <= high) ) //將小的放到目的地中
    {
        if( src[i] < src[j] )
        {
            des[k++] = src[i++];
        }
        else
        {
            des[k++] = src[j++];
        }
    }

    while( i <= mid )  //若還剩幾個尾部元素
    {
        des[k++] = src[i++];
    }

    while( j <= high ) //若還剩幾個尾部元素
    {
        des[k++] = src[j++];
    }
}

//每次分為兩路 當只剩下一個元素時,就不需要在劃分。先劃分再歸并。
void MSort(int src[], int des[], int low, int high, int max)
{
    if( low == high ) //只有一個元素,不需要歸并,結果賦給des[low]
    {
        des[low] = src[low]; 
    }
    else //如果多個元素,進行兩路劃分
    {
        int mid = (low + high) / 2;
        int* space = (int*)malloc(sizeof(int) * max);

        //遞歸進行兩路,兩路的劃分 
        //當剩下一個元素的時,遞歸劃分結束,然后開始merge歸并操作
        if( space != NULL )
        {
            MSort(src, space, low, mid, max); 
            MSort(src, space, mid+1, high, max);
            Merge(space, des, low, mid, high); //調用歸并函數進行歸并
        }

        free(space);
    }
}

void MergeSort(int array[], int len)
{
    MSort(array, array, 0, len-1, len);
}

歸并排序的Java實現

/*
 * 歸并操作(merge),也叫歸并算法,指的是將兩個已經排序的序列合并成一個序列的操作。   
 * 如設有數列{6,202,100,301,38,8,1}   
 * 初始狀態: [6] [202] [100] [301] [38] [8] [1] 比較次數   
 * i=1 [6 202 ] [ 100 301] [ 8 38] [ 1 ] 3   
 * i=2 [ 6 100 202 301 ] [ 1 8 38 ] 4   
 * i=3 [ 1 6 8 38 100 202 301 ] 4 
 */
public class MergeSort {
    public static void sort(int[] data) {
        int[] temp = new int[data.length];
        mergeSort(data, temp, 0, data.length - 1);
    }

    private static void mergeSort(int[] data, int[] temp, int l, int r) {
        int mid = (l + r) / 2;
        if (l == r)
            return;
        mergeSort(data, temp, l, mid);
        mergeSort(data, temp, mid + 1, r);

        for (int i = l; i <= r; i++) {
            temp[i] = data[i];
        }
        int i1 = l;
        int i2 = mid + 1;
        for (int cur = l; cur <= r; cur++) {
            if (i1 == mid + 1)
                data[cur] = temp[i2++];
            else if (i2 > r)
                data[cur] = temp[i1++];
            else if (temp[i1] < temp[i2])
                data[cur] = temp[i1++];
            else
                data[cur] = temp[i2++];
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個記錄插入到已排序好...
    依依玖玥閱讀 1,282評論 0 2
  • 數據結構與算法--歸并排序 歸并排序 歸并排序基于一種稱為“歸并”的簡單操作。比如考試可能會分年級排名和班級排名,...
    sunhaiyu閱讀 904評論 0 6
  • 歸并排序是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer)的一個非...
    NEXTFIND閱讀 1,014評論 0 0
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,220評論 0 52
  • 概述:排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,743評論 0 15