1. 程式人生 > >氣泡排序演算法原理及實現(超詳細)

氣泡排序演算法原理及實現(超詳細)

氣泡排序(Bubble Sort)是排序演算法裡面比較簡單的一個排序。它重複地走訪要排序的數列,一次比較兩個資料元素,如果順序不對則進行交換,並一直重複這樣的走訪操作,直到沒有要交換的資料元素為止。

氣泡排序的原理

為了更深入地理解氣泡排序的操作步驟,我們現在看一下氣泡排序的原理。

首先我們肯定有一個數組,裡面存放著待排序的元素列表,我們如果需要把比較大的元素排在前面,把小的元素排在後面,那麼需要從尾到頭開始下面的比較操作:
  1. 從尾部開始比較相鄰的兩個元素,如果尾部的元素比前面的大,就交換兩個元素的位置。
  2. 往前對每個相鄰的元素都做這樣的比較、交換操作,這樣到陣列頭部時,第 1 個元素會成為最大的元素。
  3. 重新從尾部開始第 1、2 步的操作,除了在這之前頭部已經排好的元素。
  4. 繼續對越來越少的資料進行比較、交換操作,直到沒有可比較的資料為止,排序完成。

注意,看完了這裡的操作步驟,我們可以想一下,如果從頭到尾進行操作是否可以?當然不可以,不過這樣可以完成從小到大的排序。

假如我們要把 12、35、99、18、76 這 5 個數從大到小進行排序,那麼數越大,越需要把它放在前面。氣泡排序的思想就是在每次遍歷一遍未排序的數列之後,將一個數據元素浮上去(也就是排好了一個數據)。

我們從後開始遍歷,首先比較 18 和 76,發現 76 比 18 大,就把兩個數交換順序,得到 12、35、99、76、18;接著比較 76 和 99,發現 76 比 99 小,所以不用交換順序;接著比較 99 和 35,發現 99 比 35 大,交換順序;接著比較 99 和 12,發現 99 比 12 大,交換順序。最終第 1 趟排序的結果變成了 99、12、35、76、18,排序的過程如圖 1 所示。

圖 1 第 1 趟氣泡排序的過程示例
經過第 1 趟排序,我們已經找到了最大的元素,接下來的第 2 趟排序就只對剩下的 4 個元素排序。第 2 趟排序的過程示例如圖 2 所示。

圖 2 第 2 趟氣泡排序的過程示例
經過第 2 趟排序,結果為 99、76、12、35、18。接下來應該進行第 3 趟排序了,剩下的元素不多,比較次數也在減少。

第3趟排序的結果應該是 99、76、35、12、18,接下來第 4 趟排序的結果是 99、76、35、18、12,經過 4 趟排序之後,只剩一個 12 需要排序了,這時已經沒有可比較的元素了,所以排序完成。

這個演算法讓我想起了小時候在操場排隊跑步,老師總是說:“高的站前面,低的站後面”。我們一開始並不一定會站到準確的位置上,接著老師又說:“你比前面的高,和前面的換換,還高,再和前面換換”,就這樣找到了自己的位置。

通過這個例子,你是否已經完全掌握了排序演算法的精髓呢?

氣泡排序的實現

通過對氣泡排序原理的學習,我們應該能夠很容易地寫出實現程式碼了。首先我們需要從後往前遍歷待排序陣列,然後重複這個步驟,繼續遍歷剩下的待排序的數列,這樣我們就需要一個雙重迴圈去完成這個演算法。
public class BubbleSort {
    private int[] array;
   
    public BubbleSort(int[] array) {
        this.array = array;
    }

    /**
     * 從小到大
     */
    public void sort() {
        int length = array.length;
        if (length > 0) {
            for (int i = 1; i < length; i++) {
                for (int j = 0; j < length - i; j++) {
                    if (array[j] > array[j + 1]) {
                        int temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
                    }
                }
            }
        }
    }
   
    /**
     * 從大到小
     */
    public void sort2() {
        int length = array.length;
        if (length > 0) {
            for (int i = length - 1; i > 0; i--) {
                for (int j = length - 1; j > length - 1 - i ; j--) {
                    if (array[j] > array[j - 1]) {
                        int temp = array[j];
                        array[j] = array[j - 1];
                        array[j - 1] = temp;
                    }
                }
            }
        }
    }
   
    public void print() {
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }

}
下面是氣泡排序的測試程式碼。
public class SortTest {
    public static void main(String[] args) {

        testBubbleSort();
    }
   
    /**
     * 氣泡排序
     */
    private static void testBubbleSort() {
        int[] array = {5, 9, 1, 9, 5, 3, 7, 6, 1};
        BubbleSort bubbleSort = new BubbleSort(array);
        bubbleSort.sort2();
        bubbleSort.print();
    }
}

氣泡排序的特點及效能

通過氣泡排序的演算法思想,我們發現氣泡排序演算法在每輪排序中會使一個元素排到一端,也就是最終需要 n-1 輪這樣的排序(n 為待排序的數列的長度),而在每輪排序中都需要對相鄰的兩個元素進行比較,在最壞的情況下,每次比較之後都需要交換位置,所以這裡的時間複雜度是 O(n2)。其實氣泡排序在最好的情況下,時間複雜度可以達到 O(n),這當然是在待排序的數列有序的情況下。在待排序的數列本身就是我們想要的排序結果時,時間複雜度的確是 O(n),因為只需要一輪排序並且不用交換。但是實際上這種情況很少,所以氣泡排序的平均時間複雜度是 O(n2)

對於空間複雜度來說,氣泡排序用到的額外的儲存空間只有一個,那就是用於交換位置的臨時變數,其他所有操作都是在原有待排序列上處理的,所以空間複雜度為 O(1)

氣泡排序是穩定的,因為在比較過程中,只有後一個元素比前面的元素大時才會對它們交換位置並向上冒出,對於同樣大小的元素,是不需要交換位置的,所以對於同樣大小的元素來說,相對位置是不會改變的。

氣泡排序演算法的時間複雜度其實比較高。從 1956 年開始就有人研究氣泡排序演算法,後續也有很多人對這個演算法進行改進,但結果都很一般,正如 1974 年的圖靈獎獲得者所說的:“氣泡排序除了它迷人的名字和引起的某些有趣的理論問題,似乎沒有什麼值得推薦的。”

氣泡排序的適用場景

對於氣泡排序,我們應該對它的思想進行理解,作為排序演算法學習的引導,讓我們的思維更加開闊。

雖然氣泡排序在我們的實際工作中並不會用到,其他排序演算法多多少少比氣泡排序演算法的效能更高,但是我們還是要掌握氣泡排序的思想及實現,並且在面試時還是有可能會用到。