排序算法

堆排序

基本思想

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。html

堆的定義下:

具備n個元素的序列(h1,h2,...,hn),當且僅當知足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,...,n/2)時稱之爲堆。在這裏只討論知足前者條件的堆。由堆的定義能夠看出,堆頂元素(即第一個元素)必爲最大項(大頂堆)。徹底二 叉樹能夠很直觀地表示堆的結構。堆頂爲根,其它爲左子樹、右子樹。java

思想:

初始時把要排序的數的序列看做是一棵順序存儲的二叉樹,調整它們的存儲序,使之成爲一個堆,這時堆的根節點的數最大。而後將根節點與堆的最後一個節點交換。而後對前面(n-1)個數從新調整使之成爲堆。依此類推,直到只有兩個節點的堆,並對它們做交換,最後獲得有n個節點的有序序列。從算法描述來看,堆排序須要兩個過程,一是創建堆,二是堆頂與堆的最後一個元素交換位置。因此堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。算法

示例:

  • 首先將序列構建稱爲大頂堆;(這樣知足了大頂堆那條性質:位於根節點的元素必定是當前序列的最大值)

ErRrLt.png

  • 取出當前大頂堆的根節點,將其與序列末尾元素進行交換;(此時:序列末尾的元素爲已排序的最大值;因爲交換了元素,當前位於根節點的堆並不必定知足大頂堆的性質)
  • 對交換後的n-1個序列元素進行調整,使其知足大頂堆的性質;
    ErRDsI.png
  • 重複2.3步驟,直至堆中只有1個元素爲止

實現

/**
 * 創建堆
 * @param a 待建的數組
 * @param lastIndex 須要創建的數組的最後一個元素(控制須要創建堆的長度)
 */
private void buildMaxHeap(int[] a, int lastIndex){
    // 從lastIndex處節點(最後一個節點)的父節點開始
    for(int i = (lastIndex-1)/2; i >=0 ; i--){
        // k保存正在判斷的節點
        int k=i;
        // 若是當前的節點的子節點存在
        while (k*2+1<=lastIndex){
            // biggerIndex 爲最大值的索引,先將其賦值爲左子節點
            int biggerIndex = k*2+1;
            // 若是存在右子節點,則須要比較其大小
            if(biggerIndex < lastIndex){
                // biggerIndex始終爲最大的子節點。
                if(a[biggerIndex + 1] > a[biggerIndex]){
                    biggerIndex++;
                }
            }
            // 若是k節點的值小於其較大的子節點的值,則須要交換他們
            if(a[biggerIndex] > a[k]){
                swap(a, biggerIndex, k);
                // 交換後的左子節點,、
                // 有可能小於他本身的子節點,
                // 因此須要從新進行比較排序,
                // 保證最小值在下面的節點
                k = biggerIndex;
            } else {
                break;
            }
        }
    }
}

/**
 * 交換
 */
private void swap(int[] a, int i, int j){
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

public void heapSort(int[] a){
    for(int i=0; i<a.length-1; i++){
        buildMaxHeap(a, a.length-1-i);
        swap(a, 0, a.length-1-i);
    }
}

@Test
public void heapTest(){
    int[] a={7,5,3,2,9,10,8,4,6,1};
    heapSort(a);
    System.out.println(Arrays.toString(a));
}
複製代碼

歸併排序

基本思想

歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題而後遞歸求解,而治(conquer)的階段則將分的階段獲得的各答案"修補"在一塊兒,即分而治之)。shell

操做方法

EBRpaF.md.png
EBR954.md.png

實現

/**
 * 歸併排序
 * 簡介:將兩個(或兩個以上)有序表合併成一個新的有序表 即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列
 * 時間複雜度爲O(nlogn)
 * 穩定排序方式
 * @param a 待排序數組
 */
private void mergeSort(int[] a, int low, int high){
    if(low < high){
        int mid = (low + high) >>> 1;
        mergeSort(a, low, mid);
        mergeSort(a,mid+1, high);
        merge(a, low, mid, high);
    }
}

/**
 * 將數組中low到high位置的數進行排序
 * @param a 待排序數組
 * @param low 待排的開始位置
 * @param mid 待排中間位置
 * @param high 待排結束位置
 */
private void merge(int[] a, int low, int mid, int high){
    int[] temp = new  int[high -low + 1];
    int i = low; // 左指針
    int j = mid + 1; // 右指針
    int k = 0;
    // 將較小的數移到新數組中
    while (i <= mid && j <= high){
        if(a[i] < a[j]){
            temp[k++] = a[i++];
        } else {
            temp[k++] = a[j++];
        }
    }
    // 把左邊剩餘的移到數組中
    while (i <= mid){
        temp[k++] = a[i++];
    }
    // 把右邊剩餘的移到數組中
    while (j <= high){
        temp[k++] = a[j++];
    }
    // 把新數組中的數覆蓋原數組
    for(int k2 = 0; k2 <temp.length; k2++){
        a[k2+low] = temp[k2];
    }
}

@Test
public void mergeTest(){
    int[] a={7,5,3,2,9,10,8,4,6,1};
    mergeSort(a, 0, a.length-1);
    System.out.println(Arrays.toString(a));
}
複製代碼

希爾排序

基本思想

把記錄按步長進行分組,對每組記錄採用直接插入的方法進行排序。隨着步長的縮小,所分紅的組包含的記錄就愈來愈多,當步長的值減少到1時,整個數據合成一組,構成一組有序的記錄,則完成排序。數組

操做方法

  • 先將待排序序列按照某一「增量/步長(increment)」,分割成若干個子序列。
  • 對每個子序列作直接插入排序。
  • 縮小增量/步長,繼續把整個序列按照增量方法分割成若干個子序列。
  • 繼續對每一個子序列作直接插入排序。
  • 重複上述步驟,直到增量爲1後,也就是最後一次排序,變成了一次直接插入排序。

示例

基本的希爾排序
private void shellSort(int[] a){
    for(int increment = a.length/2; increment > 0; increment /= 2){
        // 分組
        for(int i=increment; i < a.length; i++){
            // 組內排序
            for(int j=i; j >= increment; j--){
                if(a[j] < a[j-increment]){
                    int temp = a[j];
                    a[j] = a[j-increment];
                    a[j-increment] = temp;
                } else {
                    // 由於當一次循環以上時,由於前面已經排好序,
                    // 因此直接與最近的一個increment增量比較便可。
                    // 符合繼續比較,不符合直接跳過
                    break;
                }
            }
        }
    }
}
複製代碼
帶哨兵的希爾排序

減小交換次數,提升效率bash

/**
 * 哨兵希爾排序,就是將位置爲j的元素取出來放到一個變量,
 * 最後將這個值放到合適的位置
 * @param a 待排序數組a
 */
private void shellSort1(int[] a){
    int j=0;
    int temp = 0;
    for(int increment = a.length >>> 1; increment >0; increment = increment >>> 1){
        for (int i=increment; i < a.length; i++){
            temp = a[i];
            for(j=i; j>=increment; j-=increment){
                // temp 是a[j-increment] 交換後的值,
                // if裏面只是將a[j]的值變換了,
                // 而a[j-increment]中沒有變,
                // 所以來減小變換次數
                if(temp < a[j-increment]){
                    a[j] = a[j-increment];
                } else {
                    // 由於當一次循環以上時,由於前面已經排好序,
                    // 因此直接與最近的一個increment增量比較便可。
                    // 符合繼續比較,不符合直接跳過
                    break;
                }
            }
            a[j] = temp;
        }
    }
}
複製代碼

參考:
必須知道的八大種排序算法【java實現】(二) 選擇排序,插入排序,希爾算法【詳解】
內部排序(二)希爾排序的兩種實現
數據結構常見的八大排序算法(詳細整理) 圖解排序算法(四)之歸併排序數據結構

有些註解是本身的理解,註解及代碼若有不恰之處,歡迎各位留言指正。ide

相關文章
相關標籤/搜索