分治法-合並排序和快速排序


分治法是按照以下方案工作的:

  • 將問題的實例划分為同一個問題的幾個較小的實例,最好擁有同樣的規模
  • 對這些較小的實例求解(一般使用遞歸方法,但在問題規模足夠小的時候,有時會利用另一種算法以提高效率)
  • 如果必要的話,合並較小問題的解,以得到原始問題的解

分治法的流程:

clip_image001

4.1 合並排序

合並排序是成功應用分治技術的一個完美例子(書上說的)。

對於一個需要排序的數組,合並排序把它一分為二,並對每個子數組遞歸排序,然后把這兩個排好序的子數組合並為一個有序數組。

代碼實現:

/**
     * 合並排序
     * @author xiaofeig
     * @since 2015.9.16
     * @param array 要排序的數組
     * @return 返回排好序的數組
     * */
    public static int[] mergeSort(int[] array){
        if(array.length>1){
            int[] subArray1=subArray(array,0,array.length/2);
            int[] subArray2=subArray(array,array.length/2,array.length);
            subArray1=mergeSort(subArray1);
            subArray2=mergeSort(subArray2);
            return merge(subArray1,subArray2);
        }
        return array;
    }
    /**
     * 返回指定數組的子數組
     * @author xiaofeig
     * @since 2015.9.16
     * @param array 指定的數組
     * @param beginIndex 子數組的開始下標
     * @param endIndex 子數組的結束位置(不包括該元素)
     * @return 返回子數組
     * */
    public static int[] subArray(int[] array,int beginIndex,int endIndex){
        int[] result=new int[endIndex-beginIndex];
        for(int i=beginIndex;i<endIndex;i++){
            result[i-beginIndex]=array[i];
        }
        return result;
    }
    /**
     * 根據數值大小合並兩個數組
     * @author xiaofeig
     * @since 2015.9.16
     * @param subArray1 待合並的數組
     * @param subArray2 待合並的數組
     * @return 返回合並好的數組
     * */
    public static int[] merge(int[] subArray1,int[] subArray2){
        int[] result=new int[subArray1.length+subArray2.length];
        int i=0,j=0;
        while(i<subArray1.length&&j<subArray2.length){
            if(subArray1[i]>subArray2[j]){
                result[i+j]=subArray2[j];
                j++;
            }else{
                result[i+j]=subArray1[i];
                i++;
            }
        }
        if(i==subArray1.length){
            while(j<subArray2.length){
                result[i+j]=subArray2[j];
                j++;
            }
        }else{
            while(i<subArray1.length){
                result[i+j]=subArray1[i];
                i++;
            }
        }
        return result;
    }

算法分析:

當n>1時,C(n)=2C(n-2)+Cmerge(n),C(1)=0

Cmerge(n)表示合並階段進行鍵值比較的次數。最壞的情況下(比如,最小的元素輪流來自不同的數組),Cmerge(n)=n-1。所以:Cworst(n)=2Cworst(n-1)+(n-1),Cworst(1)=0。

因此,Cworst(n)屬於θ(n*log(n))。可以求得最差效率遞推式的精確解:Cworst(n)=nlog2n-n+1。

合並排序在最壞情況下的鍵值比較次數十分接近於任何基於比較的排序算法在理論上能夠達到的最少次數。合並排序的主要缺點就是該算法需要線性的額外空間。

4.2 快速排序

快速排序是另一種基於分治技術的重要排序算法。不像合並排序是按照元素在數組中的位置對它們進行划分,快速排序按照元素的值對它們進行划分。它對於給定數組中的元素進行重新排列,以得到一個快速排序的分區。在一個分區中,所有在s下標之前的元素都小於等於A[s],所有在s下標之后的元素都大於等於A[s]。

具體實現方式:在數組中選擇一個元素(中軸),選擇的策略有很多種,這里可選擇數組的第一個元素。分別從左向右和從右向左的掃描數組:從左向右的掃描(i表示下標)從第二個元素開始,自動忽略小於中軸的元素,直到遇到大於等於中軸的元素才會停止;從右向左的掃描(j表示下標)從最后一個元素開始,掃描自動忽略大於中軸的元素,直到遇到小於等於中軸的元素才停止。當兩端的掃描都停止后,會出現兩種情況(i<j或i>=j),當i<j時,交換A[i]和A[j],並繼續掃描;當i>=j時,返回j作為s的值,並繼續遞歸位於s左右兩邊的子數組。

代碼實現:

/**
 * 合並排序 * @author xiaofeig * @since 2015.9.17 * @param array 要排序的數組 * */
    public static void quickSort(int[] array){ if(array.length>1){ int s=partition(array); int[] leftPart=Arrays.copyOfRange(array, 0, s); int[] rightPart=Arrays.copyOfRange(array, s+1, array.length); quickSort(leftPart); quickSort(rightPart); //合並中軸元素的左右兩部分,包括中軸元素
            for(int i=0;i<leftPart.length;i++){ array[i]=leftPart[i]; } for(int i=0;i<rightPart.length;i++){ array[i+leftPart.length+1]=rightPart[i]; } } } /**
 * 合並排序划分區 * @author xiaofeig * @since 2015.9.17 * @param array 要分區的數組 * @return 返回中軸元素的最終下標 * */
    public static int partition(int[] array){ int i=0,j=array.length; do{ do{ i++; }while(i<array.length-1&&array[i]<array[0]); do{ j--; }while(j>1&&array[j]>array[0]); int temp=array[i]; array[i]=array[j]; array[j]=temp; }while(i<j); int temp=array[i]; array[i]=array[j]; array[j]=temp; if(array[j]<=array[0]){//中軸右側元素均大於中軸元素時無需交換
            temp=array[0]; array[0]=array[j]; array[j]=temp; }else{ j--;//中軸右側元素均大於中軸元素時須將j值降至0
 } return j; }

算法分析:

當n>1時,Cbest(n)=2Cbest(n/2)+n,Cbest(1)=0

所以,Cbest(n)屬於θ(nlog2n);對於n=2k的情況求得Cbest(n)=nlog2n

最差的情況是輸入的數組已經排過序了,如果A[0..n-1]是嚴格遞增的數組,並且我們將A[0]作為中軸,從左到右的掃描會停在A[1]上,而從右往左的掃描會一直處理到A[0]為止(我上面的代碼是掃描到A[1]),導致分列點出現在0位置上。這種情況下,鍵值比較的總次數應該等於:

Cworst(n)=(n+1)+n+…+3=(n+1)(n+2)/2-3,屬於θ(n2)

對於大小為n的隨機排列的數組,快速排序的平均鍵值比較次數記為Cavg(n)。假設分區的分裂點s(0<=s<=n-1)位於每個位置的概率都是1/n,得到下面的遞推關系式:

當n>1時,clip_image001[1]

 

 

Cavg(0)=0,Cavg(1)=1,計算結果:Cavg(n)≈2n*ln(n)≈1.38nlog2n。

因此,快速排序在平均情況下,僅比最優情況多執行38%的比較操作。此外,它的最內層循環效率非常高,使得在處理隨機排列的數組時,速度要比合並排序快


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM