學點算法(六)——數組選擇排序

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

假設你是一個農場工人,收穫了100個蘿蔔,長短不一,農場主讓你從小到大排列好售賣,你應該怎麼做?

一般做法就是選一個蘿蔔,並假設它是最小的,然後依次與其他蘿蔔比較,如果發現更小的,則換成更小的蘿蔔,一直比到最後,然後把作爲最小的蘿蔔放好。接着重新選一個蘿蔔,重複剛纔的操作,直到最後選出作爲第二小的蘿蔔放好,依次重複這個步驟,最後,蘿蔔就從小到大排好了。

從上面的場景中,我們可以發現,如果每次從無序集合中選出一個最小的或者最大的,把選出的這個最小或者最大的放到指定位置,那麼隨着每次選擇的進行,最後我們會得到一個有序的集合,這個過程就是今天的主角——選擇排序的算法過程。

我們來看對數組[6, 3, 9, 7, 8, 5, 0, 2, 4, 1]的從小到大選擇排序過程:

  1. 得到一個未排序數組[6, 3, 9, 7, 8, 5, 0, 2, 4, 1]
    在這裏插入圖片描述
  2. 在未排序部分中,選出最小值,此時最小值爲0,將0取出,放到有序部分的下一個位置上,因爲此時沒有有序部分(可以理解爲有序部分最後索引爲-1),即放到索引爲0的位置上,但是索引爲0的位置已經有一個元素了,爲了不丟失這個元素,我們交換一下這兩個元素,即將索引爲0的元素6與索引爲6的元素0交換位置,這樣就將最小值0放到了指定位置,並且也沒有丟失原來在索引0的元素6
    在這裏插入圖片描述
  3. 繼續在未排序部分中,選出最小值,此時最小值爲1,將1取出,和索引爲1的元素2交換位置。
    在這裏插入圖片描述
  4. 繼續在未排序部分中,選出最小值,此時最小值爲2,將2取出,和索引爲2的元素9交換位置。
    在這裏插入圖片描述
  5. 繼續在未排序部分中,選出最小值,此時最小值爲3,將3取出,和索引爲3的元素7交換位置。
    在這裏插入圖片描述
  6. 繼續在未排序部分中,選出最小值,此時最小值爲4,將4取出,和索引爲4的元素8交換位置。
    在這裏插入圖片描述
  7. 繼續在未排序部分中,選出最小值,此時最小值爲5,將5取出,在這一步,我們發現,5已經處於它所在的位置上,可以不用交換,而且程序想要知道不交換,也需要每次去判斷一下,與其去判斷,不如直接交換,同時還保持了編程實現的簡潔和統一。
    在這裏插入圖片描述
  8. 繼續在未排序部分中,選出最小值,此時最小值爲6,將6取出,同樣,我們還是繼續交換6
    在這裏插入圖片描述
  9. 繼續在未排序部分中,選出最小值,此時最小值爲7,將7取出,和索引爲7的元素9交換位置。
    在這裏插入圖片描述
  10. 繼續在未排序部分中,選出最小值,此時最小值爲8,將8取出,同樣,我們還是繼續交換8
    在這裏插入圖片描述
  11. 繼續在未排序部分中,選出最小值,此時最小值爲9,將9取出,同樣,我們還是繼續交換9
    在這裏插入圖片描述
  12. 至此,所有元素就位,排序完畢。

在元素就位的過程中,我們可以發現,由於要將元素放到指定位置,而不能使原來在這個位置上的元素丟失,我們就需要交換元素,而交換元素就導致了相同元素的排列的原始順序被破壞,所以數組的選擇排序是一個不穩定的排序算法。

代碼實現如下:

/**
 * 數組的選擇排序算法
 * 從小到大排序
 *
 * @param nums 待排序數組
 * @param lo   排序區間lo索引(包含)
 * @param hi   排序區間hi索引(不包含)
 */
public static void selectionSort(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;
    }
    // 排序部分待存放元素的索引
    int nextSortedIdx = 0;
    // 未排序部分的起始位置
    int unsortedStartIdx = 0;
    while (unsortedStartIdx < hi) {
        // 還有元素未排序
        // 找到最小值元素所在的索引位置
        int minIdx = findMinIdx(nums, unsortedStartIdx, hi);
        // 交換排序部分待存放元素和最小值元素
        int tmp = nums[minIdx];
        nums[minIdx] = nums[nextSortedIdx];
        nums[nextSortedIdx] = tmp;
        // 排序部分待存放元素的索引+1
        nextSortedIdx++;
        // 未排序部分的起始位置+1
        unsortedStartIdx++;
    }
    // 無未排序元素,排序完畢
}

/**
 * 在[lo, hi)區間內找到最小值元素所在的索引位置
 * @param nums 待查找數組
 * @param lo   查找區間lo索引(包含)
 * @param hi   查找區間hi索引(不包含)
 */
private static int findMinIdx(int[] nums, int lo, int hi) {
    // 假設最小值元素所在索引位置爲lo
    int minIdx = lo;
    for (int i = lo; i < hi; i++) {
        if (nums[i] < nums[minIdx]) {
            // 如果發現比當前假設元素小的,則更新索引位置
            minIdx = i;
        }
    }
    return minIdx;
}

測試代碼如下:

int[] nums = {6, 3, 9, 7, 8, 5, 0, 2, 4, 1};
System.out.println("排序前:" + Arrays.toString(nums));
selectionSort(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]

符合我們的預期。

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