【排序演算法動畫解】排序介紹及氣泡排序

二十二畫程式設計師 發表於 2021-06-10

本文為系列專題【資料結構和演算法:簡單方法】的第 12 篇文章。

  1. 資料結構 | 順序表
  2. 資料結構 | 連結串列
  3. 資料結構 | 棧
  4. 資料結構 | 佇列
  5. 資料結構 | 雙連結串列和迴圈連結串列
  6. 資料結構 | 二叉樹的概念和原理
  7. 資料結構 | 二叉樹的建立及遍歷實現
  8. 資料結構 | 線索二叉樹
  9. 資料結構 | 二叉堆
  10. 演算法 | 順序查詢和二分查詢
  11. 資料結構 | 二叉查詢樹

本文先簡單介紹一下什麼是排序,然後再結合動畫介紹暴力排序和氣泡排序。

1. 什麼是排序?

排序在日常生活中無處不在。比如考試成績的排名、體育課的從低到高的隊形、網購時按價格升序排列或降序排列等等。

姓名 學號 班級 成績
張三 1001 2班 100
李四 1003 1班 90
王五 1000 3班 80
趙六 1006 2班 70

在該表格中,我們稱一行資料為一條記錄,其中姓名、學號等資料項是關鍵字,關鍵字是我們排序的依據。關鍵字可以是主關鍵字或次關鍵字,甚至是若干資料項的組合。

比如,在該表格中,我們是按照成績(次關鍵字)從高到低降序排序的。也可以按照學號排序,或者按照學號和成績同時排序:先按成績降序排列,成績相同的,按照學號升序排列。

所以,對多條記錄排序的本質是對關鍵字的排序,如果排序依據是多個關鍵字,可以轉化為單個關鍵字的排序。

所謂排序,是指按指定的關鍵字,將某個序列的所有元素以有序的方式排列起來

如何實現排序呢?

比如上體育課時按身高的排序,就是比較兩個人的身高,然後矮的站前面,高的站後面。如果高的在前,矮的在後,就交換位置;否則就用不動。

沒錯,排序是藉助比較和交換來實現的。比較的是關鍵字,這是我們排序的依據。交換的是兩個元素的位置,通常我們不真的交換位置,而是藉助中間變數的賦值來實現交換位置的效果。

/*一個交換函式,交換陣列中下標為i和j的元素位置*/
void swap(int *array, int i, int j)
{
    int tmp = array[i];
    array[i] = array[j];
    array[j] = tmp;
}

在實現排序演算法時,我們應當追求更少次數的比較和交換,這樣才能提高演算法的效能

為了後面說明問題方便起見,這裡約定幾個規則:

  • 只對陣列排序,並約定陣列長度 length = 10
  • 全部排序演算法均按升序排序
  • 由於是針對陣列,所以關鍵字就是其元素值本身。比較關鍵字即是比較元素值,不再區分。

2. 暴力排序(Violent Sort)

所謂暴力,是指沒有任何技巧而言的方式,它直接粗暴地遍歷真個陣列,從而不停的進行比較和交換。這是最容易理解的排序演算法。

核心思想:從陣列的第一個元素開始,將每個元素作為基準元素,基準元素和其後面的所有元素比較,如果基準元素大於其後的某個元素,則與其交換位置;否則位置不變。

當基準元素和其後的所有元素比較交換完後,最小值一定被排好了,此時就說一排序完成了,每輪排序有若干次迴圈,用於比較和交換。像這樣進行若干輪的排序後,排序就完成了。

我們需要兩個變數,用於標記兩個進行比較的元素下標:

  • 變數 i:某個元素的下標;
  • 變數 j:下標 i 之後的下標。

我們需要兩個巢狀迴圈:

  • 外層迴圈控制輪數;
  • 內層迴圈控制每輪的比較和交換。

動態過程如下:

在這裡插入圖片描述

程式碼實現如下:

/* 暴力排序
 * array : 陣列
 * length : 陣列長度
 */
void violent_sort(int *array, int length)
{
    int i, j;
    // 外層迴圈,遍歷陣列,控制輪數
    for (i = 0; i < length; i++) {
        // 內層迴圈,迴圈比較 array[i] 和其之後的元素,控制迴圈次數
        for (j = i + 1; j < length; j++) {
            if (array[i] > array[j]) { // 如果大於則交換
                swap(array, i, j);
            }
        }
    }
}

通過以上的暴力排序,我們能夠初體會排序的思想,即不斷地比較和交換

暴力排序有其明顯的缺點,即每個元素都要和其之後的所有的元素比較,這就很容易破壞整個陣列的原來順序,比如在上面的動態過程中,元素 40 在某次交換後被交換到了更靠後的位置。為了排好某個元素,而對其之後的所有元素大動干戈,甚至破壞了它們原來之間的順序,這就影響了效率。

3. 氣泡排序(Bubble Sort)

氣泡排序是一種很簡單的排序演算法,由暴力排序改進而來。

核心思想:比較相鄰的兩個元素,當前者大於後者時,交換二者位置;否則位置不變。

注意:氣泡排序是相鄰的兩個元素比較並交換,而暴力排序是某個元素和其之後的所有元素比較並交換。

當陣列中所有的相鄰元素進行兩兩比較和交換後,一定會找到一個最大值,並且最大值被交換到陣列尾處,此時說一輪排序完成了,一輪排序中有若干次迴圈,用於比較和交換。像這樣進行若干輪排序後,排序就完成了。

我們需要兩個變數:

  • 變數 i:用於控制輪數;

  • 變數 j:元素下標,j+1 為其相鄰元素的下標。

我們需要兩個巢狀迴圈:

  • 外層迴圈控制輪數
  • 內層迴圈控制每輪所有相鄰元素的比較和交換

動態過程如下:

【排序演算法動畫解】排序介紹及氣泡排序

程式碼實現如下:

/* 氣泡排序
 * array : 陣列
 * length : 陣列長度
 */
void bubble_sort(int *array, int length)
{
    int i, j;
    // 外層迴圈,控制輪數。每一輪排好一個元素
    for (i = 0; i < length; i++) {
        // 內層迴圈,迴圈比較陣列中未排序的元素
        for (j = 0; j < length - i - 1; j++) {
            // 若前者大於後者則交換
            if (array[j] > array[j + 1]) {
                swap(array, j, j + 1);
            }
        }
    }
}

由於是相鄰的兩個元素進行比較,所以一輪比較後,一定有一個最大的元素被交換到最右邊,像“大泡泡”浮到水面上一樣,所以我們稱其為“氣泡排序”。

氣泡排序雖然改善了暴力排序的缺點,但仍然具有可優化的地方。

在上述動態過程中,第 7 輪(i = 6)時就已經完成排序了,但後面仍然進行了 3 輪的比較。這最後 3 輪是完全沒必要的,所以我們可以改進一下,避免沒必要的比較。

方法就是增加一個標誌變數 unsorted,用於標誌陣列是否已經有序,unsorted = 0 時陣列有序,unsorted = 1 時陣列無序。

unsorted 通過記錄在某輪排序中是否發生了交換,如果沒發生,就意味著已經有序。

void bubble_sort_v3(int *array, int length)
{
    int i, j;
    int unsorted = 1; // 標識變數
    for (i = 0; i < length && unsorted; i++) {
        unsorted = 0;
        for (j = 0; j < length - i - 1; j++) {
            if (array[j] > array[j + 1]) {
                swap(array, j, j + 1);
                unsorted = 1; // 發生交換,說明尚未有序
            }
        }
    }
}

雖然氣泡排序可以避免一些不必要的比較和交換,但是氣泡排序本質仍和暴力排序相似,即不斷地大量交換,或是某個元素和其後所有元素的交換,或是兩個相鄰元素的交換。通過動圖也可以看出,要使一個元素排到其正確的位置上,我們往往需要和多個元素相交換,這是影響效率的“硬傷”

完整程式碼請移步至 GitHub | Gitee 獲取。

如有錯誤,還請指正。

如果覺得寫的不錯,可以點個贊和關注。後續會有更多資料結構和演算法相關文章。

【排序演算法動畫解】排序介紹及氣泡排序