3、排序之冒泡、插入、選擇

1、衡量一個排序算法

1.一、排序算法的執行效率

  1. 最好狀況、最壞狀況、平均狀況時間複雜度
  2. 時間複雜度的係數、常數 、低階
    時間複雜度反應的是數據規模 n 很大的時候的一個增加趨勢,因此它表示的時候會忽略係數、常數、低階。
    可是實際的軟件開發中,咱們排序的多是10個、 100個、 1000個這樣規模很小的數據,因此,在對同一階時間複雜度的排序算法性能對比的時候,咱們就要把係數、常數、低階也考慮進來。
  3. 比較次數和交換(或移動)次數
    冒泡、插入、選擇都是基於比較的排序算法。基於比較的排序算法的執行過程,會涉及兩種操做,一種是元素比較大小,另外一種是元素交換或移動。
    因此,若是咱們在分析排序算法的執行效率的時候,應該把比較次數和交換(或移動)次數也考慮進去。

1.二、排序算法的內存消耗

  • 算法的內存消耗能夠經過空間複雜度來衡量,排序算法也不例外。
  • 不過,針對排序算法的空間複雜度,還有一個新的概念, 原地排序(Sorted in place)。
    原地排序算法,就是特指空間複雜度是 O(1) 的排序算法。咱們今天講的三種排序算法,冒泡、插入、選擇原地排序算法。

1.三、排序算法的穩定性

  • 若是待排序的序列中存在值相等的元素,通過排序以後,相等元素之間原有的前後順序不變。
  • 好比一組數據 2, 9, 3, 4, 8, 3,按照大小排序以後就是 2, 3, 3, 4, 8, 9。
  • 這組數據裏有兩個 3。通過某種排序算法排序以後,若是兩個 3 的先後順序沒有改變,那咱們就把這種排序算法叫做穩定的排序算法。
  • 若是先後順序發生變化,那對應的排序算法就叫做不穩定的排序算法。

2、冒泡排序

  • 冒泡排序只會操做相鄰的兩個數據。每次冒泡操做都會對相鄰的兩個元素進行比較。
  • 看是否知足大小關係要求。若是不知足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置。
  • 重複 n 次,就完成了 n 個數據的排序工做。
  • 冒泡的過程只涉及相鄰數據的交換操做,只須要常量級的臨時空間,因此它的空間複雜度爲 O(1),是一個原地排序算法
  • 在冒泡排序中,只有交換才能夠改變兩個元素的先後順序。爲了保證冒泡排序算法的穩定性,當有相鄰的兩個元素大小相等的時候。
  • 咱們不作交換,相同大小的數據在排序先後不會改變順序,因此冒泡排序是穩定的排序算法
  • 最好狀況下,要排序的數據已是有序的了,咱們只須要進行一次冒泡操做,就能夠結束了,因此最好狀況時間複雜度是 O(n)
  • 而最壞的狀況是,要排序的數據恰好是倒序排列的,咱們須要進行 n 次冒泡操做,因此最壞狀況時間複雜度爲 O(n²)
冒泡排序

能夠看出,通過一次冒泡操做以後, 6這個元素已經存儲在正確的位置上。要想完成全部數據的排序,咱們只要進行6次這樣的冒泡操做就好了。
冒泡排序java

public static void bubbleSort(int[] arr) {
    if (arr.length <= 1)
        return;
    for (int i = 0; i < arr.length; i++) {
        boolean b = false;
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                b = true;
            }
        }
        if (!b) {
            break;
        }
    }
}

3、插入排序(Insertion Sort)

插入排序
  • 將數組中的數據分爲兩個區間, 已排序區間和未排序區間。
  • 初始已排序區間只有一個元素,就是數組的第一個元素。
  • 插入算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間數據一直有序。
  • 重複這個過程,直到未排序區間中元素爲空,算法結束。
插入排序
  • 插入排序算法的運行並不須要額外的存儲空間,因此空間複雜度是 O(1),也就是說,這是一個原地排序算法
  • 在插入排序中,對於值相同的元素,咱們能夠選擇將後面出現的元素,插入到前面出現元素的後面,這樣就能夠保持原有的先後順序不變,
  • 因此插入排序是穩定的排序算法
  • 若是要排序的數據已是有序的,咱們並不須要搬移任何數據。
  • 若是咱們從尾到頭在有序數據組裏面查找插入位置,每次只須要比較一個數據就能肯定插入的位置。
  • 因此這種狀況下,最好是時間複雜度爲 O(n)。注意,這裏是從尾到頭遍歷已經有序的數據。
  • 若是數組是倒序的,每次插入都至關於在數組的第一個位置插入新的數據,因此須要移動大量的數據,因此最壞狀況時間複雜度爲O(n²)
  • 在數組中插入一個數據的平均時間複雜度是 O(n)。因此,對於插入排序來講,每次插入操做都至關於在數組中插入一個數據,循環執行n次插入操做,因此平均時間複雜度爲O(n2)。
public static void insertionSort(int[] arr) {
    int n = arr.length;
    if (n <= 1) {
        return;
    }
    for (int i = 0; i < n; i++) {
        int value = arr[i];
        int j = i - 1;
        // 查找插入的位置
        for (; j >= 0; --j) {
            if (arr[j] > value) {
                arr[j + 1] = arr[j];//移動數據
            } else {
                break;
            }
        }
        arr[j + 1] = value;//插入數據
    }
}

4、選擇排序(Selection Sort)

  • 選擇排序空間複雜度爲 O(1),是一種原地排序算法。
  • 選擇排序的最好狀況時間複雜度、最壞狀況和平均狀況時間複雜度都爲O(n²)
  • 選擇排序是一種不穩定的排序算法,選擇排序每次都要找剩餘未排序元素中的最小值,並和前面的元素交換位置,這樣破壞了穩定性。
public static void selectionSort(int[] arr) {
    int length = arr.length;
    if (length == 1) {
        return;
    }
    int x;
    for (int i = 0; i < length; i++) {
        x = i;
        for (int j = i + 1; j < length; j++) {
            if (arr[x] > arr[j]) {
               x = j;
            }
        }
        if(x != i){
            int tmp = arr[i];
            arr[i] = arr[x];
            arr[x] = tmp;
        }
    }
}

總結一下

空間複雜度 是否穩定 最好時間複雜度 最壞時間複雜度 平均時間複雜度
冒泡排序 Q(1) Q(n) Q(n²) Q(n²)
插入排序 Q(1) Q(n) Q(n²) Q(n²)
選擇排序 Q(1) Q(n²) Q(n²) Q(n²)
相關文章
相關標籤/搜索