學點算法(四)——數組插入排序

今天來學習數組的插入排序算法。

在講算法之前,我們先來想象這樣一個場景:我們要上體育課,體育老師要求同學們從高到矮排好隊。當同學們都排好隊的時候,小甲同學遲到了。這時候小甲同學隨便站個地方可不行,因爲這樣隊伍就不是排好的了,所以要將小甲同學插入到隊伍中,使隊伍重新又是從高到矮排好的。小甲同學對比自己和同學們的身高,找到自己的位置,排到隊伍裏,就完成了隊伍的排序。

這個將一個無序元素插入到n個元素的有序集合,使新的(n+1)個元素的集合變成重新有序的動作就是插入排序的基本思想。藉助這種思想,我們就可以把一個數組分成有序的和無序的兩部分,不斷將無序的部分裏面的元素取出來插入到有序的部分中,使數組最終變爲有序的。因爲一個元素本身就有序了,我們可以把第一個元素作爲最初的有序部分。
排序示意圖
我們來看對數組[6, 3, 9, 7, 8, 5, 0, 2, 4, 1]的從小到大排序過程:

  1. 得到一個未排序數組[6, 3, 9, 7, 8, 5, 0, 2, 4, 1]
    在這裏插入圖片描述
  2. 得到首元素6,因爲只有一個元素,所以直接放入有序部分。
    在這裏插入圖片描述
  3. 取出下一個元素3,將其依次與有序部分的元素對比,此時有序部分元素只有6,對比後發現3小於6,所以3要作爲首元素插入,得到新的有序部分[3, 6]
    在這裏插入圖片描述
  4. 取出下一個元素9,將其依次與有序部分的元素對比,發現9比有序部分的最後一個元素6還要大,所以將9插入到有序部分的末尾,得到新的有序部分[3, 6, 9]
    在這裏插入圖片描述
  5. 取出下一個元素7,將其依次與有序部分的元素對比,發現79小,比6大,所以將7插入到69之間,得到新的有序部分[3, 6, 7, 9]
    在這裏插入圖片描述
  6. 取出下一個元素8,將其依次與有序部分的元素對比,發現89小,比7大,所以將8插入到79之間,得到新的有序部分[3, 6, 7, 8, 9]
    在這裏插入圖片描述
  7. 取出下一個元素5,將其依次與有序部分的元素對比,發現56小,比3大,所以將5插入到36之間,得到新的有序部分[3, 5, 6, 7, 8, 9]在這裏插入圖片描述
  8. 取出下一個元素0,將其依次與有序部分的元素對比,發現0比有序部分的首元素3還要小,所以將0作爲有序部分的首元素插入,得到新的有序部分[0, 3, 5, 6, 7, 8, 9]
    在這裏插入圖片描述
  9. 取出下一個元素2,將其依次與有序部分的元素對比,發現23小,比0大,所以將2插入到03之間,得到新的有序部分[0, 2, 3, 5, 6, 7, 8, 9]
    在這裏插入圖片描述
  10. 取出下一個元素4,將其依次與有序部分的元素對比,發現45小,比3大,所以將4插入到35之間,得到新的有序部分[0, 2, 3, 4, 5, 6, 7, 8, 9]
    在這裏插入圖片描述
  11. 取出最後一個元素1,將其依次與有序部分的元素對比,發現12小,比0大,所以將1插入到02之間,得到新的有序部分[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    在這裏插入圖片描述
  12. 至此,所有元素歸入有序部分,排序完成。

算法實現代碼如下:

/**
 * 數組的插入排序算法
 * 從小到大排序
 *
 * @param nums 待排序數組
 * @param lo   排序區間lo索引(包含)
 * @param hi   排序區間hi索引(不包含)
 */
public static void insertionSort(int[] nums, int lo, int hi) {
    // 數組爲null則直接返回
    if (nums == null) {
        return;
    }
    // 索引檢查
    if (lo < 0 || nums.length <= lo) {
        throw new IllegalArgumentException("lo索引必須大於0並且小於數組長度,數組長度:" + nums.length);
    }
    if (hi < 0 || nums.length < hi) {
        throw new IllegalArgumentException("hi索引必須大於0並且小於等於數組長度,數組長度:" + nums.length);
    }
    if (hi <= lo) {
        // lo索引必須小於hi索引(等於也不行,因爲區間是左閉右開,如果等於,區間內元素數量就爲0了)
        throw new IllegalArgumentException("lo索引必須小於hi索引");
    }
    if (lo + 1 >= hi) {
        // 區間元素個數最多爲1
        // 無需排序
        return;
    }
    for (int i = lo + 1; i < hi; i++) {
        // 獲取當前元素
        int e = nums[i];
        // 從當前位置的前一個元素開始,往前查找,直到索引爲-1
        // 或找到第一個小於等於當前元素的值(等於時可保證穩定排序)
        int j = i - 1;
        for (; j >= lo; j--) {
            if (nums[j] <= e) {
                // 如果找到一個元素小於等於當前元素
                // 退出循環
                break;
            }
        }
        // 此時j索引表示當前元素插入位置的上一個位置
        // 所以插入位置索引爲j+1
        // j可能爲-1,此方法仍然適用
        int insertIdx = j + 1;
        // 如果insertIdx == i,說明此時元素已經就位了,無需插入
        if (insertIdx < i) {
            // 元素未就位,需要插入
            // 插入方法爲從insertIdx(包含)到i(不包含)之間的元素往後移一個位置,
            // 當前元素放到insertIdx的位置上
            System.arraycopy(nums, insertIdx, nums, insertIdx + 1, i - insertIdx);
            nums[insertIdx] = e;
        }
    }
}

測試代碼如下:

int[] nums = {6, 3, 9, 7, 8, 5, 0, 2, 4, 1};
System.out.println("排序前:" + Arrays.toString(nums));
insertionSort(nums, 0, nums.length);
System.out.println("排序後:" + Arrays.toString(nums));

輸出如下:

排序前:[6, 3, 9, 7, 8, 5, 0, 2, 4, 1]
排序後:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

符合我們的預期。

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