1. 程式人生 > >演算法:排序演算法之氣泡排序

演算法:排序演算法之氣泡排序

排序算法系列目錄說明

  • 氣泡排序(Bubble Sort)
  • 插入排序(Insertion Sort)
  • 希爾排序(Shell Sort)
  • 選擇排序(Selection Sort)
  • 快速排序(Quick Sort)
  • 歸併排序(Merge Sort)
  • 堆排序(Heap Sort)
  • 計數排序(Counting Sort)
  • 桶排序(Bucket Sort)
  • 基數排序(Radix Sort)

排序演算法簡介說明

1. 定義

將一組雜亂無章的資料按一定的規律順次排列起來。例如:

輸入:a1,a2,a3,…,an

輸出:a1’,a2’,a3’,…,an’(滿足a1′ <= a2′ <= a3′ <= … <= an’排列)

2. 演算法效能評估術語言

穩定:如果a原本在b前面,而a=b時,排序之後a仍然在b的前面。
不穩定:如果a原本在b的前面,而a=b時,排序之後a可能出現在b的後面。

內排序:所有排序操作都在記憶體中完成。
外排序:通常是由於資料太大,不能同時存放在記憶體中,根據排序過程的需要而在外存與記憶體之間 資料傳輸才能進行。

時間複雜度:時間頻度,一個演算法執行所耗費的時間。演算法中通常用資料比較次數與資料移動次數 進行衡量。
空間複雜度:演算法執行所需要的記憶體大小。

氣泡排序(Bubble Sort)

1. 基本思想

氣泡排序是一種交換排序,核心是冒泡,把陣列中最小的那個往上冒,冒的過程就是和他相鄰的元素交換。

重複走訪要排序的數列,通過兩兩比較相鄰記錄的排序碼。排序過程中每次從後往前冒一個最小值,且每次能確定一個數在序列中的最終位置。若發生逆序,則交換;有倆種方式進行冒泡,一種是先把小的冒泡到前邊去,另一種是把大的元素冒泡到後邊。

趣味解釋:
有一群泡泡,其中一個泡泡跑到一個泡小妹說,小妹小妹你過來咱倆比比誰大,小妹說哇你好大,於是他跑到了泡小妹前面,又跟前面的一個泡大哥說,大哥,咱倆比比誰大唄。泡大哥看了他一眼他就老實了。這就是內層的for,那個泡泡跟每個人都比一次。

話說那個泡泡剛老實下來,另一個泡泡又開始跟別人比誰大了,這就是外層的for,每個泡泡都會做一次跟其他泡泡比個沒完的事。

2. 實現邏輯
  • 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  • 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
  • 針對所有的元素重複以上的步驟,除了最後一個。
  • 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。

通過兩層迴圈控制:
- 第一個迴圈(外迴圈),負責把需要冒泡的那個數字排除在外;
- 第二個迴圈(內迴圈),負責兩兩比較交換。

3. 動圖演示bubble_sort

這裡寫圖片描述

4. 效能分析
  • 平均時間複雜度:O(N^2)
  • 最佳時間複雜度:O(N)
  • 最差時間複雜度:O(N^2)
  • 空間複雜度:O(1)
  • 排序方式:In-place
  • 穩定性:穩定

解析說明:
氣泡排序涉及相鄰兩兩資料的比較,故需要巢狀兩層 for 迴圈來控制。
外層迴圈 n 次,內層最多時迴圈 n – 1次、最少迴圈 0 次,平均迴圈(n-1)/2;
所以迴圈體內總的比較交換次數為:n*(n-1) / 2 = (n^2-n)/2
按照計算時間複雜度的規則,去掉常數、去掉最高項係數,其複雜度為O(N^2)
最優的空間複雜度為開始元素已排序,則空間複雜度為 0;
最差的空間複雜度為開始元素為逆排序,則空間複雜度為 O(N);
平均的空間複雜度為O(1)

注:

  • n:資料規模
  • k:”桶”的個數
  • In-place:佔用常數記憶體,不佔用額外記憶體
  • Out-place:佔用額外記憶體
5. 程式碼實現(C++版)
// 氣泡排序
void bubble_sort(int arr[], int len)  
{  
    int i, j;  
    for (i = 0; i < len; i++)  
        for (j = 1; j < len - i; j++)  
            if (arr[j - 1] > arr[j])  
                swap(arr[j - 1], arr[j]);  
}
6. 優化改進
6.1 改進方法①

場景一:
在某次遍歷中如果沒有資料交換,說明整個陣列已經有序。若初始序列就是排序好的,如果用基礎的氣泡排序方法,仍然還要比較O(N^2)次,但無交換次數。

改進思路:
通過設定標誌位來記錄此次遍歷有無資料交換,進而可以判斷是否要繼續迴圈,設定一個flag標記,當在一趟序列中沒有發生交換,則該序列已排序好,但優化後排序的時間複雜度沒有發生量級的改變。

改進程式碼(C++版):

// 氣泡排序改進
void bubble_sort(int arr[], int len)
{
    for (int i = 0; i < len-1; i++){        //比較n-1次
        bool exchange = true;               //冒泡的改進,若在一趟中沒有發生逆序,則該序列已有序
        for (int j = len-1; j >i; j--){     //每次從後邊冒出一個最小值
            if (arr[j] < arr[j - 1]){       //發生逆序,則交換
                swap(arr[j], arr[j - 1]);
                exchange = false;
            }
        }
        if (exchange){
            return;
        }
    }
}

6.2 改進方法②
場景二:
如果有100個數的陣列,僅前面10個無序,後面90個都已排好序且都大於前面10個數字,那麼在第一趟遍歷後,最後發生交換的位置必定小於10,且這個位置之後的資料必定已經有序了。

改進思路:
記錄某次遍歷時最後發生資料交換的位置pos,這個位置之後的資料顯然已經有序了。因此通過記錄最後發生資料交換的位置就可以確定下次迴圈的範圍了。由於pos位置之後的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可。

改進程式碼(C++版):

// 氣泡排序改進② 
void bubble_sort(int arr[], int len)  
{  
    int j, k;  
    int flag;  
    flag = len;  
    while (flag > 0)  
    {  
        k = flag;  
        flag = 0;  
        for (j = 1; j < k; j++)  
            if (arr[j - 1] > arr[j])  
            {  
                swap(arr[j - 1], arr[j]);  
                flag = j;  
            }  
    }  
}

總結

氣泡排序畢竟是一種效率低下的排序方法,在資料規模很小時,可以採用。資料規模比較大時,建議採用其它排序方法。其他相關排序演算法會在後續系列中逐一來分析說明,敬請關注!