歸併演算法詳解

derekzhan發表於2010-07-28

MergeSort歸併排序

[1] 歸併排序的原理

1.1 將兩個有序陣列合併成一個有序陣列

兩個陣列,每一個陣列內部都是有序的,比如:
a{5,7} 和 b{6,11,37}

我們申請足夠大的空間,來放排好序的陣列。比如這個陣列叫 c{}。

每次取兩個陣列中最小的數,進行比較,小的取出放入 c{}。
如此將兩個陣列中的元素都取完,全部放入 c{},c{} 中就是一個有序的陣列。比如:

第1次:5和6比較,5較小,所以 a{7}, b{6,11,37} -> c{5}
第2次:7和6比較,6較小,所以 a{7}, b{11,37} -> c{5,6}
第3次:7和11比較,7較小,所以 a{}, b{11,37} -> c{5,6,7}
第4次:a{} 空了,所以 b{} 中剩下的都是排好序的大的,所以 a{}, b{} -> c{5,6,7,11,37}

1.2 遞迴產生有序陣列

每次:把一個無序陣列,分成前後兩半(根據陣列中的元素個數,就可以確定到哪裡是一半),首先對前一半進行遞迴呼叫,然後對後一半進行遞迴呼叫。最後歸併,產生一個完整的有序陣列。

比如:{7, 5, 37, 6, 11},

一共有5個元素,5/2=2,所以分成 {7,5} {37,6,11}。

   對{7,5} 進行遞迴呼叫,一共有2個元素,2/2=1,所以分成 {7} {5}。
   對{7} 進行遞迴呼叫,發現到頭了,返回。
   對{5} 進行遞迴呼叫,發現到頭了,返回。
   於是歸併 {7} {5},得到有序陣列 {5,7},返回。

   對{37,6,11} 進行遞迴呼叫,一共有3個元素 3/2=1,所以分成 {37} {6,11}。
   對{37} 進行遞迴呼叫,發現到頭了,返回。

   對{6,11} 進行遞迴呼叫,一共有2個元素 2/2=1,所以分成 {6} {11}。
     對{6} 進行遞迴呼叫,發現到頭了,返回。
     對{11} 進行遞迴呼叫,發現到頭勒,返回。
     於是歸併 {6} {11},得到有序陣列 {6,11},返回。

   於是歸併 {37} {6,11},得到有序陣列 {6,11,37},返回。

於是歸併 {5,7} {6,11,37},得到有序陣列 {5,6,7,11,37},返回。排序完成。

[2] 歸併排序的實現

package testAlgorithm;

import java.util.Arrays;

/**
 * @author derek zhan
 * @version 
 */
@SuppressWarnings("unchecked")
public class MergeSortTest
{
    //排序中的臨時陣列
    private Comparable [] tmpArray ;
    
    public static void main(String[] args)
    {
        Integer [] obj = {1,4,7,2,5,8};
        MergeSortTest mst = new MergeSortTest();
        mst.sort(obj);
        System.out.println(Arrays.toString(obj));
        Integer[] obj2 = {8,5,2,7,4,1};
        mst.mergeSort2(obj2);
        System.out.println(Arrays.toString(obj2));
    }
    
    /** 
    * *利用歸併排序演算法對陣列obj進行排序 
    */ 
    public void sort(Comparable[] obj) { 
        tmpArray = new Comparable[obj.length];// 初始化中間陣列 
        mergeSort1(obj, 0, obj.length - 1); // 歸併排序 
        tmpArray = null; 
    } 
    
    //第一個種方法
    /**
     * 遞迴劃分成2個陣列
     * @param obj 要排序的陣列
     * @param left 陣列中第一個元素下標
     * @param right 陣列最後一個元素的下標
     */
    public void mergeSort1(Comparable[] obj,int left,int right){
        if(left < right){//只要不是陣列的最後一個元素
            int center = (left+right)/2;
            mergeSort1(obj, left, center); //劃分成左陣列
            mergeSort1(obj, center+1, right);//劃分成右陣列
            merge1(obj, left, center, right);//合併2一個陣列並排序
        }
    }
    //第二種方法
    public void mergeSort2(Comparable[] obj){
        int n = obj.length;
        if(n<=1) return; //如果只有一個元素則返回.
        Comparable left[] = new Comparable[n/2]; //左陣列
        Comparable right[] = new Comparable[n-n/2];  //右陣列
        for(int i=0,leftPos=left.length; leftPos<obj.length; i++,leftPos++){
            if(i<left.length){ //給左陣列賦值
                left[i] = obj[i];
            }
            //給右陣列賦值
            right[i] = obj[leftPos];
            
        }
        mergeSort2(left);
        mergeSort2(right);
        merge2(obj, left, right);
    }
    
    /**
     * 合併陣列並排序
     * @param obj 要合併的陣列
     * @param left 左邊陣列第一個元素下標
     * @param center 左邊陣列最後一個元素下標
     * @param right 右邊陣列最後一個元素下標
     */
    public void merge1(Comparable[] obj,int left,int center,int right){
        int midd = center+1;
        int temp = left;
        int third = left;
        while(left <= center && midd <= right){
            if(obj[left].compareTo(obj[midd])<=0){ //比較2個陣列中的值,把小值放入臨時陣列
                tmpArray[third++] = obj[left++]; 
            }else{
                tmpArray[third++] = obj[midd++]; 
            }
        }
        //如果第一個陣列已經全部比較完了,那麼我們只要直接複製第二個陣列的條目到合併陣列中即可 
        while(midd<=right){
            tmpArray[third++] = obj[midd++]; 
        }
        //如果第二個陣列已經全部比較完了,那麼我們只要直接複製第一個陣列的條目到合併陣列中即可 
        while(left <= center){
            tmpArray[third++] = obj[left++]; 
        }
        //拷貝bridge to obj陣列
        while(temp<=right){
            obj[temp] = tmpArray[temp];
            temp++;
        }
    }
    
    //第二種合併方法
    public void merge2(Comparable[] obj,Comparable[] left,Comparable[] right){
        int leftLength = left.length-1;
        int rightLength = right.length-1;
        int k=0; 
        int leftPos=0,rightPos=0; // left陣列下標, right陣列下標 
        while(leftPos<=leftLength||rightPos<=rightLength){ 
            Comparable temp = null; 
            if(leftPos>leftLength){  //如果第一個陣列已經全部比較完了,那麼我們只要直接複製第二個陣列的條目到合併陣列中即可 
                temp=right[rightPos++]; 
            }else if(rightPos>rightLength){  //如果第二個陣列已經全部比較完了,那麼我們只要直接複製第一個陣列的條目到合併陣列中即可 
                temp=left[leftPos++]; 
            }else if(left[leftPos].compareTo(right[rightPos])>0){  //把比較的兩個條目中關鍵值小的放到合併陣列中 
                temp=right[rightPos++]; 
            }else{ 
                temp=left[leftPos++]; 
            } 
            obj[k++]=temp; //把比較完的值放入陣列
            } 
        } 
}

  

最後執行結果:

[1, 2, 4, 5, 7, 8]
[1, 2, 4, 5, 7, 8]

 

相關文章