圖解選擇排序與插入排序

9龍發表於2019-04-15

上一篇詳述了氣泡排序及其優化,有興趣的可以看看:

如何優化氣泡排序?

一、選擇排序(SelectionSort)

  • 演算法思想:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
  • 排序過程:(預設升序)
  1. 從原序列中找到最小值,與陣列第一個元素交換;
  2. 除第一個元素外,從剩下未排序的序列中找到最小值,與陣列第二個元素交換;
  3. 共N-1趟,每趟都找到未排序的最小值,放到已排序的序列後面。

如圖所示,每一趟找到未排序的最小值,並放到有序序列的後面(即當前趟對應於陣列中的第幾個元素)。

  • java實現選擇排序
 private static <T extends Comparable<? super T>> void selectionSort(T[] nums) {
        if (null == nums || nums.length == 0) {
            throw new RuntimeException("陣列為null或長度為0");
        }
        int length = nums.length;
        int minValueIndex = 0;
        T temp = null;
        for (int i = 0; i < length - 1; i++) {
            minValueIndex = i;
            for (int j = i + 1; j < length; j++) {
                if (nums[j].compareTo(nums[minValueIndex]) < 0) {
                    minValueIndex = j;
                }
            }
            if (minValueIndex != i) {
                temp = nums[i];
                nums[i] = nums[minValueIndex];
                nums[minValueIndex] = temp;
            }
        }
    }
複製程式碼
  • 時間、空間複雜度及穩定性分析:
  1. 最好時間複雜度:最好情況是輸入序列已經升序排列,需要比較n*(n-1)/2次,但不需要交換元素,即交換次數為:0;所以最好時間複雜度O(n^2)
  2. 最壞時間複雜度:最壞情況是輸入序列是逆序的,則每一趟都需要交換。即需要比較n*(n-1)/2次,元素交換次數為:n-1次。所以最壞時間複雜度還是O(n^2)
  3. 平均時間複雜度:O(n^2)
  4. 空間複雜度:只用到一個臨時變數,所以空間複雜度O(1)
  5. 穩定性:不穩定排序。如序列3,5,3,1。第一次交換結果為1,5,3,3,我們發現原序列的第一個3排在了第二個3的後面。

二、插入排序(InsertSort)

  • 演算法思想:通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。

  • 排序過程:(預設升序)

    InsertionSort 和打撲克牌時,從牌桌上逐一拿起撲克牌,在手上排序的程式相同。

    舉例:

    Input: {4, 3, 8, 5, 2, 6, 1, 7}。

  1. 首先拿起第一張牌, 手上有 {4}。

  2. 拿起第二張牌 3, 把 3insert 到手上的牌 {4}, 得到 {3 ,4}。

  3. 拿起第三張牌 8, 把 8 insert 到手上的牌 {3,4 }, 得到 {3 ,4,8}。

  4. 以此類推。

    插入排序由N-1趟排序組成。對於p=1到N-1趟排序後,插入排序保證從位置0到位置p上的元素為已排序狀態。即插入排序利用了從位置0到p-1位置上已經有序的條件,將位置p上的元素向前查詢適當的位置插入此元素。

    如圖所示:在第p趟,我們將位置p上的元素向左移動,直到它在前p+1個元素(包括當前位置的元素)中的正確位置被找到。

  • java實現插入排序

    private static <T extends Comparable<? super T>> void insertSort(T[] nums) {
          if (null == nums || nums.length == 0) {
              throw new RuntimeException("陣列為null或長度為0");
          }
          int length = nums.length;
          T temp = null;
          int i = 0;
          for (int p = 1; p < length; p++) {
              temp = nums[p];
              for (i = p; i > 0 && (temp.compareTo(nums[i - 1]) < 0); i--) {
                  nums[i] = nums[i - 1];
              }
              nums[i] = temp;
          }
      }
    複製程式碼
  • 時間、空間複雜度及穩定性分析:

  1. 最好時間複雜度:最好情況就是,序列已經是升序排列了,在這種情況下,需要進行的比較操作需n-1次即可。即最好時間複雜度O(n)
  2. 最壞時間複雜度:最壞情況就是,序列是降序排列,那麼總共需要n(n-1)/2次比較;移動次數(賦值操作)是比較次數減去n-1次(因為每一次迴圈的比較都比賦值多一次,共n-1次迴圈),即n(n-1)/2 - (n-1);所以最壞時間複雜度O(n^2)
  3. 平均時間複雜度:O(n^2)
  4. 空間複雜度:只用到一個臨時變數,所以空間複雜度O(1)
  5. 穩定性:穩定。

三、總結

選擇排序的主要優點與資料移動有關。如果某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個將被移到其最終位置上,因此對n個元素的表進行排序總共進行n-1次交換。在所有的完全依靠交換去移動元素的排序方法中,選擇排序屬於非常好的一種。選擇排序最好、最壞時間複雜度都為O(n^2),空間複雜度為O(1),屬於不穩定排序。

插入排序不適合對於資料量比較大的排序應用。但是,如果需要排序的資料量很小,例如,量級小於千;或者若已知輸入元素大致上按照順序排列,那麼插入排序還是一個不錯的選擇。插入排序最好時間複雜度為O(n)、最壞時間複雜度為O(n^2),空間複雜度為O(1),屬於穩定排序。

相關文章