插入排序——直接插入排序與希爾排序

#排序算法–直接插入排序與希爾排序
今天這裏介紹插入排序中的直接插入排序及針對其進行的優化排序方法Shell排序。

直接插入排序

直接插入排序是人們能想到的最基本的排序方法之一,這種排序方式將待排序列分爲兩部分,一部分是已排好序的,另一部分沒有排好序。最開始已排序部分中只包含一個元素,因此自然是有序的,每次從另一部分,也就是待排序列中取一個元素與前面已經有序的序列依次比較:將比它大的元素依次右移一個位置,直到找到已排序部分中比它小或最小的那個元素時,即確定了待排元素的正確位置。

動態演示圖如下:
直接插入排序示意圖
代碼實現如下:

public class InsertSort {

    public static void main(String[] args) {
        Integer[] arr = {8, 6, 1, 3, 9, 7, 2, 5, 4};

        // 外層循環:每次將無序部分第一個元素插入到前面有序部分中正確位置
        for (int i = 1; i < arr.length; i++) {
            int j = i - 1;
            int tmp = arr[i];

            // 內層循環:將外層元素依次與已排序列元素比較
            while (j >= 0 && arr[j] > tmp) {
                // 保證已排序部分有序性,較大元素右移
                arr[j + 1] = arr[j];
                j--;
            }
            // 找到排序位置後插入
            arr[j+1] = tmp;
        }
        
		// test-打印
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

}

時間複雜度最優情況下爲O(n),即已經有序的序列,只需每個元素比較一次,平均和最差情況爲O(n^2)。

Shell排序

希爾排序以直接插入排序爲基礎,發揚了直接插入排序在小序列、基本有序情況下速度較快的優點,採用分治的思想將原始序列進行分組,再針對分組內的元素分別排序,然後減小分組數量,增多分組內元素個數,直到合併分組爲一個。

動態演示圖如下:
Shell排序示意圖
代碼實現如下(演示圖與代碼不對應):

/**
 * @description: 希爾排序基本實現
 * @author: liyaguang
 * @create: 2018-08-31 13:41
 **/
public class ShellSort {

    public static void main(String[] args) {
        Integer[] arr = {8, 6, 1, 3, 9, 7, 2, 5, 4};

        // 確定分組個數,即確定了每個子序列,也是子序列步長
        for (int i = arr.length / 2; i > 0; i /= 2) {
            // 循環每個子序列,進行排序
            for (int j = 0; j < i; j++) {
                // j、j+i、j+i+i、……、n-i

                // 調用插入排序方法,傳入子序列值
                modInsertSort(arr, j, i);
            }
        }

        // 若增量序列中不能保證最後的間距爲1,則執行一次掃尾工作,此方法內不需要
        // modInsert(arr, 0, 1);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

    }

    /**
     * @param arr      待排數組
     * @param startPos 待排子序列起始位置
     * @param delta    步長/分組個數
     */
    public static void modInsertSort(Integer[] arr, int startPos, int delta) {
        for (int i = startPos + delta; i < arr.length - startPos; i += delta) {

            for (int j = i; j > startPos; j -= delta) {
                if (arr[j] < arr[j - delta]) {
                    // swap(arr, y, y -= i);
                    int tmp = arr[j - delta];
                    arr[j - delta] = arr[j];
                    arr[j] = tmp;
                } else {
                    break;
                }
            }
        }
    }
}

希爾排序時間複雜度跟選擇的分組策略息息相關,上面我們實現的“分組數每次除以2遞減”的方式實際上時間複雜度仍爲O(n ^ 2) ,選擇適合的增量序列將有效的改善希爾排序的效率,有的能達到O(n ^ 3/2)、O(n ^ 5/4),有的甚至能達到O(n ^ 7/6),很接近O(nlogn)。

小結

直接插入排序是我們生活中數量不多的排序是最可能想到的一種方法,例如給一個班的成績排序,我們用一張足夠長的空白紙先記錄下待排序列中第一個學生的成績,在拿出第二個學生的成績與第一個比對,找到合適位置,再拿第三個待排數據與前兩個比對,以此實現最終排序的效果。
生活中數據量小、待排序列基本有序的情況下使用直接插入排序很直觀、效果也很好,時間複雜度能控制在O(n),而希爾排序則是在直接插入排序的基礎上充分發揮了其優點,採用分組、分治的方式進行化大爲小,再逐步合併的過程中又充分利用了前面基本有序的結果。

參考資料:
《數據結構與算法》
Insertion sort-Wikiwand
Shellsort-wikiwand

歡迎關注我的公衆號,瞭解更多內容:
我的公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章