1. 程式人生 > >JAVA演算法之高階排序

JAVA演算法之高階排序

  本章介紹兩種高階排序,希爾排序和快速排序,這兩種排序比之前講到的簡單排序都要快很多;希爾排序大約需要O(N*(logN)2)的時間,快速排序的時間複雜度為(N*logN),這兩種演算法和我們在講遞迴的時候講到的歸併排序不同,不需要大量的輔助儲存空間,快速排序是所有通用排序演算法中最快的排序演算法。

希爾排序:

  希爾排序是基於插入排序的,希爾排序在插入排序的基礎之上通過加大插入排序元素之間的間隔,並在這些間隔元素之間進行插入排序,使資料實現大跨度的移動,從而使排序更有效率,我們習慣將排序時資料項之間的間隔稱為增量,用h來表示,下圖表示了十個資料項,以增量為4進行第一趟排序後的結果

  通過上面的一趟排序之後我們可以看到元素離他們的最終有序序列位置都很近了,希爾排序就是通過建立這種交錯有序的資料項集合,從而提高排序的效率,當上面完成了4增量的排序之後,就可以進行普通的插入排序,這樣就比普通的插入排序的效率要高很多。

  上面對於有十個資料項的陣列初始增量我們設定為4,對於資料項更大的陣列我們的初始增量也應該設定得更大,這裡我們使用Knuth提出的間隔序列,序列的通項表示式為h=3*h+1,假設我們資料項的個數為100,那麼通過Knuth間隔序列1,4,13,40,121,364,1093這裡1093大於了我們的資料項1000,所以我們取初始的增量為364然後通過Knuth間隔序列依次減小我們的增量值,最終達到我們想要的一個個排序的效果,接下來我們給出希爾排序的java程式碼

public class ArrayShell {

    public long[] theArray;
    private int nElems;

    
public ArrayShell(int size) { this.theArray = new long[size]; this.nElems = 0; } public void insert(long value) { theArray[nElems++] = value; } public void shellSort() { //首先找到初始增量 int h = 1; int outer, inner; while (h <= nElems/3) { h
= 3 * h + 1; } while (h > 0) { //以下就是普通的插入排序,只是步長換為h即可 for (outer = h; outer < nElems; outer++) { long temp = theArray[outer]; //增量為h,所以我們首個比較的元素從h開始,h和第一個索引為0的元素比較大小、h+1和索引為1比較是否交換。然後以此類推 inner = outer; while (inner > h - 1 && temp < theArray[inner - h]) { //需要進行資料交換的元素的滿足條件 theArray[inner] = theArray[inner - h]; inner -= h; } theArray[inner] = temp; } //從最大增量一直遞減到1做插入排序 h = (h - 1) / 3; } } }

 希爾排序中間隔序列互質很重要,他能是每一趟排序更有可能保持前一趟已排序好的效果。

快速排序

  快速排序是基於劃分演算法之上的一種排序演算法,首先我們介紹一下劃分演算法的基本原理

  • 劃分

  劃分的基本原理就是把資料分為兩組,使關鍵值大於特定值的資料在一組,使關鍵值小於特定值的資料在另一組,比如我們日常生活中將家距離辦公點15km以內和以外的僱員分為兩組。劃分演算法中我們將兩個標記分別指向陣列的兩頭,左邊的標記leftPtr ,向右移動,右邊的標記 rightPtr,向左移動,當leftPtr遇到比樞紐小的資料項時,繼續向右移動,當遇到比樞紐大的資料項時就停下來,同樣當rightPtr遇到比樞紐大的數值的時候繼續向左移動,遇到比樞紐小的就停下來,然後需要交換這兩個資料項。交換之後繼續移動指標,重複上面的步驟,知道兩個標記的值相等的時候則劃分演算法完成。

  接下來我們看劃分演算法的java程式碼

class ArrayPar {
    public Long[] theArray;
    private int nElems;
    public ArrayPar(int max) {
        theArray = new Long[max];
        this.nElems = 0;
    }
    public void insert(Long value) {
        theArray[nElems] = value;
        nElems++;
    }
    public int partitionIt(int leftPtr, int rightPtr, long pivot) {
        while (true) {
            //當leftPtr遇到比樞紐小的資料項時,繼續向右移動(即 leftPtr++),當遇到比樞紐大的資料項時就停下來
            while (leftPtr < rightPtr && theArray[leftPtr] <= pivot)    //防止索引越界加上leftPtr<rightPtr的判斷
                leftPtr++;
            //當rightPtr遇到比樞紐大的資料項時,繼續向左移動(即 rightPtr--),當遇到比樞紐大的資料項時就停下來
            while (rightPtr > leftPtr && theArray[rightPtr] >= pivot)
                rightPtr--;
            //當leftPtr標記大於等於right標記的時候結束外層迴圈,否則交換兩個標記的資料項
            if (leftPtr >= rightPtr)
                break;
            else
                swap(leftPtr, rightPtr);
        }
        return leftPtr;
    }
    /**交換資料方法*/
    public void swap(int dex1, int dex2) {
        long temp = theArray[dex1];
        theArray[dex1] = theArray[dex2];
        theArray[dex2] = temp;
    }
}
  • 快速排序

  快速排序的執行時間為O(N*logN)級,快速排序是基於劃分演算法之上的,利用遞迴的思想的一種排序演算法,這裡我們選擇陣列的最右邊的元素作為樞紐,在劃分演算法完成之後,需要在之前的演算法的基礎上加一步,將樞紐項和右陣列的起始項進行位置交換,交換後的樞紐值的順序就是最終的順序,然後在利用遞迴將劃分後的左右陣列進行上述步驟。首先我們來看看快速排序的java程式碼

class ArrayPar {
    public Long[] theArray;
    private int nElems;

    public ArrayPar(int max) {
        theArray = new Long[max];
        this.nElems = 0;
    }

    public void insert(Long value) {
        theArray[nElems] = value;
        nElems++;
    }

    public int partitionIt(int leftPtr, int rightPtr, long pivot) {
        int right = rightPtr;
        while (true) {
            //當leftPtr遇到比樞紐小的資料項時,繼續向右移動(即 leftPtr++),當遇到比樞紐大的資料項時就停下來
            while (leftPtr < rightPtr && theArray[leftPtr] <= pivot)    //防止索引越界加上leftPtr<rightPtr的判斷
                leftPtr++;
            //當rightPtr遇到比樞紐大的資料項時,繼續向左移動(即 rightPtr--),當遇到比樞紐大的資料項時就停下來
            while (rightPtr > leftPtr && theArray[rightPtr] >= pivot)
                rightPtr--;
            //當leftPtr標記大於等於right標記的時候結束外層迴圈,否則交換兩個標記的資料項
            if (leftPtr >= rightPtr)
                break;
            else
                swap(leftPtr, rightPtr);
        }
        swap(leftPtr,right);    //最後將當前樞紐數值放入對應的排序位置
        return leftPtr;
    }

    /**
     * 交換資料方法
     */
    public void swap(int dex1, int dex2) {
        long temp = theArray[dex1];
        theArray[dex1] = theArray[dex2];
        theArray[dex2] = temp;
    }
    /***快速排序的方法*/
    public void recQuickSort(int left, int right) {
        if (right - left <= 0)  //這裡是遞迴的基值條件,當只有一個數據項的時候結束遞迴
            return;
        else {
            long pivot = theArray[right];     //選擇最右邊的資料作為劃分的樞紐資料
            int partition = partitionIt(left, right, pivot);    //呼叫劃分的演算法
            //然後將劃分好的兩部分利用遞迴的思想進行再次劃分排序
            recQuickSort(left, partition - 1);
            recQuickSort(partition + 1, right);
        }
    }
}

下圖顯示了快速排序的過程

上面就是希爾排序演算法和快速排序演算法的所有內容