1. 程式人生 > >資料結構和演算法 | 插入排序演算法原理及實現和優化

資料結構和演算法 | 插入排序演算法原理及實現和優化

插入排序演算法是所有排序方法中最簡單的一種演算法,其主要的實現思想是將資料按照一定的順序一個一個的插入到有序的表中,最終得到的序列就是已經排序好的資料。

直接插入排序是插入排序演算法中的一種,採用的方法是:在新增新的記錄時,使用順序查詢的方式找到其要插入的位置,然後將新記錄插入。

很多初學者所說的插入排序,實際上指的就是直接插入排序演算法,插入排序演算法還包括折半插入排序2-路插入排序表插入排序希爾排序等,後序文章都會一一講到。

所以要完成插入排序,就需要找到這個待插入元素的位置。下面我們一起看看插入排序的具體操作原理。

插入排序的原理

插入排序實際上把待排序的數列分為了兩部分,一部分已排好序,另一部分待排序。我們下面還是以一組實際資料為例進行講解。例如採用直接插入排序演算法將無序表{3,1,7,5,2,4,9,6}進行升序排序的過程為:

  • 首先考慮記錄 3 ,由於插入排序剛開始,有序表中沒有任何記錄,所以 3 可以直接新增到有序表中,則有序表和無序表可以如圖所示: 在這裡插入圖片描述
  • 向有序表中插入記錄 1 時,同有序表中記錄 3 進行比較,1<3,所以插入到記錄 3 的左側,如圖所示: 在這裡插入圖片描述
  • 向有序表插入記錄 7 時,同有序表中記錄 3 進行比較,3<7,所以插入到記錄 3 的右側,如圖所示: 在這裡插入圖片描述
  • 向有序表中插入記錄 5 時,同有序表中記錄 7 進行比較,5<7,同時 5>3,所以插入到 3 和 7 中間,如圖所示: 在這裡插入圖片描述
  • 向有序表插入記錄 2 時,同有序表中記錄 7進行比較,2<7,再同 5,3,1分別進行比較,最終確定 2 位於 1 和 3 中間,如圖所示: 在這裡插入圖片描述
  • 照此規律,依次將無序表中的記錄 4,9 和 6插入到有序表中,如圖所示: 在這裡插入圖片描述

接下來我們總結一下直接插入排序的整個執行過程:

  1. 首先需要明確待排序的數列由兩部分組成,一部分是已排好序的部分,另一部分是待排序的部分;
  2. 接著我們每次選擇待排序的部分的第 1 個元素,分別與前面的元素進行比較。當大於前面的元素時,可以直接進入已排好序的部分;當小於前面的元素時,需要把這個元素拿出來,將前面的元素後移一位,繼續與前面的元素相比,直到比較完陣列的第 1 個元素或者出現一個元素小於我們拿出來的這個元素,這時停止比較、移動,直接把這個元素放到當時的空位上;
  3. 一直重複步驟 2,當待排序的部分已經沒有元素可進行插入時,停止操作,當前的數列為已排好序的數列。

插入排序的實現

插入排序的實現程式碼已經可以寫出來了。首先外層肯定有個大迴圈,迴圈這個待排序的部分的數列,內層是分別與前 1 個元素進行比較、移動,直到找到位置進行插入為止。

下面我們看看插入排序的程式碼實現。

public class InsertSort {
    public static void main(String[] args) {
        int[] array = {5, 9, 1, 9, 5, 3, 7, 6, 1}; // 待排序陣列
        sort(array);
        print(array);
    }

    /** 從小到大 */
    public static void sort(int array[]) {
        int length = array.length;
        // 迴圈待排序的部分的數列
        // 第一個資料(下標為0的資料)由於插入排序剛開始,有序表中沒有任何記錄,可以直接新增到有序表中
        for (int i = 1; i < length; i++) {
            int temp = array[i];
            int j = i;
            // 如果前面的元素小於temp,則向後移
            for (; j > 0 && array[j - 1] > temp; j--) {
                array[j] = array[j - 1];
            }
            // 前一個元素(array[j - 1])和後一個元素(array[j])是相同的
            // 在下一輪時,當array[j - 1]小於或等於temp時,將temp插入array[j](即上一輪的array[j - 1])
            array[j] = temp;
        }
    }

    /**
     * 列印陣列
     */
    public static void print(int array[]) {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + "   ");
        }
        System.out.println();
    }
}

插入排序的特點及效能

插入排序的操作很簡單,而且我們通過上面的例項及原理可以知道,插入排序在數列近似有序時,效率會比較高,因為這樣會減少比較和移動的次數。

插入排序的時間複雜度是 O(n2),我們會發現這個實現是個雙重巢狀迴圈,外層執行n遍,內層在最壞的情況下執行 n 遍,而且除了比較操作還有移動操作。最好的情況是數列近似有序,這時一部分內層迴圈只需要比較及移動較少的次數即可完成排序。如果數列本身已經排好序,那麼插入排序也可以達到線性時間複雜度及 O(n),所以我們應該明確地認識到,使用插入排序演算法進行排序時,數列越近似有序,效能就越高。

插入排序的空間複雜度是 O(1),是常量級的,由於在採用插入排序時,我們只需要使用一個額外的空間來儲存這個“拿出來”的元素,所以插入排序只需要額外的一個空間去做排序,這是常量級的空間消耗。

插入排序是穩定的,由於是陣列內部自己排序,把後面的部分按前後順序一點點地比較、移動,可以保持相對順序不變,所以插入排序是穩定的排序演算法

插入排序的適用場景

插入排序的效能並不是很好,和氣泡排序也算是“難兄難弟”了。但插入排序也有一個好處就是所佔用的空間很少,只有一個儲存臨時變數的額外空間就夠了。

插入排序由於其時間複雜度並不是很好,所以很少會被單獨使用。在所有的基本排序演算法中,在一般情況下我們可以直接選擇快速排序,因為這個排序演算法已經夠用了。

由於在數列近似有序時,效能會比較好,而且對於元素較少的情況,時間複雜度就算是 O(n2) 也不會消耗太多的效能,所以插入排序並非一無是處。

前面提到,在快速排序的分割槽規模達到一定的值比如 10 時,我們會改用插入排序演算法去排序那個分割槽的資料。而快速排序的最後的資料往往是近似有序的,所以使用快速排序的效能並不一定會有多好,這時使用插入排序的實際效能往往會更好些。所以很多程式語言在內部對快速排序的實現也是在分割槽的元素數量達到了一定小的規模時,改用插入排序對分割槽的資料元素進行排序操作。