【一起學習排序演算法】2 氣泡排序

莫林發表於2018-11-20

氣泡排序 Bubble sort

本系列的文章列表和相關說明,請檢視【一起學習排序演算法】0 序言
也可以直接到github上檢視完整的文章和原始碼!

原理

先看看Wikipedia的定義:

Bubble sort is a simple sorting algorithm that repeatedly steps through the list to be sorted, compares each pair of adjacent items and swaps them if they are in the wrong order. The pass through the list is repeated until no swaps are needed, which indicates that the list is sorted.

所以氣泡排序就是,每次比較相鄰的兩個元素,如果順序不對,則交換元素。每一次迭代,最大的元素會排到列表的一側。然後重複這個步驟,直到不需要交換元素,該陣列即有序了。

圖示

可以通過動畫演示理解, 以下網上找的兩個動畫。如果你想操作不同的引數來演示,可以上這個網站visualgo.net動手試試。

圖示1

圖示2

程式碼實現

關於程式碼,README中程式碼只有實現演算法的函式,具體執行的程式碼,請檢視該目錄下的檔案。

初始冒泡

程式碼如下, 看bubble_sort_1.js

const bubbleSort = (array) => {
    // 不修改原陣列
    const originValues = array.slice(); 

    // 迭代次數 陣列長度-1
    for (let i = 0; i < originValues.length - 1; i++) {
        // 兩兩比較,該迭代的最大數,移動到右側相應位置
        for (let j = 0; j < originValues.length - 1 - i; j++) {
            // 如果前一個數,大於後一個數,交換
            if (originValues[j] > originValues[j + 1]) {
                const tmp = originValues[j];
                originValues[j] = originValues[j + 1];
                originValues[j + 1] = tmp;
            }
        }
    }

    return originValues;
};
複製程式碼

程式碼其實已經很明顯了。最外層迴圈控制迭代次數,內層迴圈兩兩比較,把較大的數往右移動。不過有幾點要提一下:

  • 最外層迴圈為 (length-1)
    這個看很多實現都是外層迴圈length次,其實最後一次多餘了,因為只要前 n-1 都排序之後,第一個數肯定是最小的數了。
  • 內層迴圈可以忽略已經排好序的元素
    每過n輪,則最右側的n個元素肯定有序了。交換排序的時候,可以忽略這些元素。所以 內層迴圈的終止遊標是length-1-i

複雜度分析:
很明顯,不管陣列正序還是逆序,複雜度都是O(n2),所以最優複雜度和最壞複雜度都是O(n2)。 可資料上都是說,氣泡排序的最優複雜度是O(n)啊,那上面這種實現,肯定可以優化。
仔細覆盤上面的流程發現: 如果陣列正序,比較一輪陣列之後,後面還會傻傻地重複進行比較。而這其實是沒有必要的。只要在某一輪比較中,沒有發生元素互換,就可以確認陣列已經有序了

改進的冒泡

程式碼如下, 看bubble_sort_2.js

const bubbleSort = (array) => {
    // 不修改原陣列
    const originValues = array.slice(); 

    let swapped = true;
    do {
        // 標記該次迭代是否交換順序,如果沒有,代表列表已經有序
        swapped = false;
        for (let i = 0; i < originValues.length - 1; i++) {
            // 如果前一元素大於後一元素,交換順序
            if (originValues[i] > originValues[i + 1]) {
                const tmp = originValues[i];
                originValues[i] = originValues[i + 1];
                originValues[i + 1] = tmp;
                // 如果有交換,標記繼續下一輪
                swapped = true;
            }
        }
    } while (swapped);

    return originValues;
};
複製程式碼

複雜度分析:
經過上面的改造:當陣列正序時,也就是最優複雜度達到了O(n);當陣列逆序時,為最壞複雜度,為O(n2)。
咋一看,貌似是最終解了。但覆盤之後,發現每輪已經排序的元素,還會重複去比較。所以還可以小優化一下。

冒泡再修改

程式碼如下, 看bubble_sort_3.js

const bubbleSort = (array) => {
    // 不修改原陣列
    const originValues = array.slice(); 

    let swapped = true;
    let hasOrderedCnt = 0; // 已排序個數
    do {
        // 標記該次迭代是否交換順序,如果沒有,代表列表已經有序
        swapped = false;
        for (let i = 0; i < originValues.length - 1 - hasOrderedCnt; i++) {
            // 如果前一元素大於後一元素,交換順序
            if (originValues[i] > originValues[i + 1]) {
                const tmp = originValues[i];
                originValues[i] = originValues[i + 1];
                originValues[i + 1] = tmp;
                swapped = true;
            }
        }
        // 每一輪之後,都有一個元素排好順序
        hasOrderedCnt++;
    } while (swapped);

    return originValues;
};
複製程式碼

用了一個變數hasOrderedCnt來記錄已經排序的個數,這樣內迴圈就不要去比較已經排序的元素了。

演算法分析

  • 時間複雜度
    經過幾輪修改,陣列正序時,最優複雜度可以達到O(n);逆序時,最差複雜度O(n2)。

  • 穩定性
    演算法中,每次只有前一個元素大於後一個元素,才會進行交換。所以數值相同的兩個元素,不會發生位置互換,所以可以保持之前前後順序。故,氣泡排序是穩定的排序

總結

本章節介紹了幾種氣泡排序的實現。演算法思想還是算簡單的,但也是效率比較慢的。雖然比較簡單,但還是有很多變種,例如左右冒泡、從大到小的排序、陣列元素不是數值等等,都需要自己動手去寫才能理解透。

參考

[1] 動畫演示
[2] tutorials point 教程
[3] The Bubble sort algorithm
[4] Sorting Algorithms

相關文章