1. 程式人生 > >插入排序----希爾排序-----(本文給出了另一種直接插入排序)

插入排序----希爾排序-----(本文給出了另一種直接插入排序)

希爾排序原理如下(純手打):

假設有下面一個數組

99, 38, 65, 82, 26, 13, 27, 49, 55, 1

第一趟排序

首先設定一個增量inc,比如說最開始的增量為陣列長度的一半即 arr.length/2 = 5,則上面的陣列可以分成如下幾組(相同顏色的為一組):
這裡寫圖片描述

將每組中的元素進行比較,小的放在前面,大的放在後面(即進行直接插入排序)則第一趟排序後的陣列為
這裡寫圖片描述

第二趟排序

將增量設定為第一趟排序時增量的一半,即inc此時為5/2 = 2,則上面的陣列可以分成如下幾組(相同顏色的為一組)
這裡寫圖片描述
然後將分好組的資料分別按照直接插入排序的方式進行比較,排序後的結果為
這裡寫圖片描述

第三趟排序

將增量設定為第一趟排序時增量的一半,即inc此時為2/2 = 1,則上面的陣列可以分成如下幾組(相同顏色的為一組)
這裡寫圖片描述
此時陣列變為1組(基本有序),對這一組進行直接插入排序,得到的結果就是原始陣列排序後的陣列:
這裡寫圖片描述

總結一下:

由上面的原理介紹可以看出:
①當增量為inc時,共可以分成inc-1個小組,且每個小組的第一個元素分別為0,1,2…,inc-1,inc
②在每個小組內其實就是分別進行了直接插入排序
③該演算法的終止條件為inc>0或者說inc = 1

結論4是程式碼2的理論基礎

④從索引inc開始,之後的每一個數都至少是所在小組中的第二個數,且inc是第一小組的第2個,inc+1是第二小組(如果有的話)的第二個…..所以遍歷inc及其之後的每一個數時,都可以通過該數所在索引i找到該小組中前面的數,分別為i-1*inc,i-2*inc….i-n*inc直到i-n*inc>=0時為止.由此可以通過對比遍歷到的數arr[i]與前面已經排好序(這裡一定要注意)的arr[i-1*inc],arr[i-2*inc]….,arr[i-n*inc]進行一一比較,如果遍歷到的數字arr[i]小於前面的數,前面的數就往後移動inc個位置,直到找到一個不大於arr[i]的數,也就找到了arr[i]應該插入的正確位置.

按照上訴思路我寫出的程式碼如下:

程式碼1:

package cn.nrsc.sort;

public class ShellSort {
    public static void main(String[] args) {
        int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };

        shellSort(arr);

        System.out.println("排序後:");
        for (int i : arr) {
            System.out
.println(i); } } private static void shellSort(int[] arr) { int inc = arr.length / 2;// 初始增量 while (inc > 0) { for (int i = 0; i < inc; i++) { // 拆分後陣列的首個元素 for (int j = i; j < arr.length; j += inc) {//遍歷獲得以i為首個元素的小組 //下面其實就是直接插入排序演算法 int k; int tmp = arr[j];// 新遍歷的值等待插入到前面的有序陣列中 // 通過下面的迴圈將比tmp大的數字往後移inc位,並找到不比tmp大的數字的下標j // 因為比tmp大的數字都往後移了一位,則第j+inc位的數字移動到了j+2inc位,j+inc位給空出來了 // 並且由該演算法的思想可知,tmp正好應該放在j的後inc位,即j+inc位 for (k = j - inc; k >= i; k -= inc) { if (arr[k] > tmp) { arr[k + inc] = arr[k]; } else { break; } } arr[k + inc] = tmp; } } inc /= 2; // 增量減半 } } }

程式碼2:

package cn.nrsc.sort;

public class ShellSort {
    public static void main(String[] args) {
        int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };

        shellSort(arr);

        System.out.println("排序後:");
        for (int i : arr) {
            System.out.println(i);
        }
    }

    private static void shellSort(int[] arr) {
        // 控制增量
        for (int inc = arr.length / 2; inc > 0; inc /= 2) {
            // 從按照增量所分小組的第2個數開始進行遍歷----inc為第一小組的第2個數,inc+1為第2小組(如果有的話)的第二個數....
            for (int i = inc; i < arr.length; i++) {

                int tmp = arr[i];// 新遍歷的值等待插入到前面的有序陣列
                int j = i;  // 這裡之所以要將i賦給一個新的變數,
                            // 是因為在下面的迴圈中要通過該變數的變化,來找到arr[i]之前的陣列
                // 通過下面的迴圈①將大於tmp的數都向後移動了inc位,②找到tmp需要插入的位置j
                // tmp的位置是j是因為先用arr[j-inc]與tmp進行了比較
                while (j - inc >= 0 && arr[j - inc] > tmp) {
                    arr[j] = arr[j - inc];
                    j = j - inc;
                }
                arr[j] = tmp;

            }
        }
    }

}

由總結4還可以得到另一種形式的直接插入排序演算法:

package cn.nrsc.sort;

public class InsertSort {
    public static void main(String[] args) {
        int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };

        insertSort(arr);

        System.out.println("排序後:");
        for (int i : arr) {
            System.out.println(i);
        }
    }

    private static void insertSort(int[] arr) {
        // 從陣列中的第二個數開始往前看
        for (int i = 1; i < arr.length; i++) {

            int tmp = arr[i];// 新遍歷的值等待插入到前面的有序陣列
            int j = i;  // 這裡之所以要將i賦給一個新的變數,
                        // 是因為在下面的迴圈中要通過該變數的變化,來找到arr[i]之前的陣列

            //通過下面的迴圈①將大於tmp的數都向後移動了一位,②找到tmp需要插入的位置j
            //tmp的位置是j是因為先用arr[j-1]與tmp進行了比較
            while (j - 1 >= 0 && arr[j - 1] > tmp) {
                arr[j] = arr[j - 1];
                j = j - 1;
            }
            //將tmp插入到位置j
            arr[j] = tmp;

        }
    }

}

最後說一點

希爾排序的最差情況下的時間複雜度為Θ(N²),即最好最壞情況下都是N²,比如說對於下面的陣列,前幾個增量內的迴圈沒起到任何作用,只有增量為1時才起到了排序的效果,則前幾個增量相當於白跑了好幾趟—-具體的更深入的研究大家可以自行探索.
這裡寫圖片描述