[排序算法一]冒泡排序


冒泡排序(Bubble Sort),是一種計算機科學領域的較簡單的排序算法。
它重復地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果順序(如從大到小、首字母從Z到A)錯誤就把他們交換過來。走訪元素的工作是重復地進行直到沒有相鄰元素需要交換,也就是說該元素列已經排序完成。
這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端(升序或降序排列),就如同碳酸飲料中二氧化碳的氣泡最終會上浮到頂端一樣,故名“冒泡排序”。

冒泡排序的特點

  • 時間復雜度:最壞情況O(n^2),最好情況O(n)
  • 排序方式:In-place,不需要額外內存

基本做法(從小到大)

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

avatar

public void Sort1(int[] num)
{
    Console.WriteLine("方法1:");

    var count1 = 0;
    var count2 = 0;

    for (var i = 0; i < num.Length - 1; i++)
    {
        for (var j = i + 1; j < num.Length; j++)
        {
            count1++;
            if (num[i] <= num[j]) continue;

            count2++;
            var temp = num[j];
            num[j] = num[i];
            num[i] = temp;
        }
    }

    Console.WriteLine($"結果:{string.Join(",", num)};循環次數:{count1};數據交換次數:{count2}");
}

以上內容,除了代碼全部來自網絡,盜圖請諒解,因為做這么個圖是在太難了

通過代碼,我們發現,冒泡算法的運行次數其實應該是 (n-1)+(n-2)+(n-3)+...,等於 n(n-1)/2 次,所以得出結論,冒泡算的時間復雜度是O(n^2)

但是,上面介紹不是說有最好的情況下(數據本身就是從小到大排列)時間復雜度是 O(n)嗎?

其實,上面的圖片介紹和實際算法都存在一些問題,是有可以優化的地方的。

從文字角度上說,既然叫冒泡算法,我們想想水里氣泡的形態,一般都是從底部升起到水面,所以,為了更符合實際情況,我們的比較工作應該從數組的尾部開始,把最小(從小到大)的元素慢慢移動到數組的最前的位置。

所以這里先修改一版代碼,把比較的順序倒過來,不是把最大的往后移,而是把最小的往前移

public void Sort2(int[] numbers)
{
    Console.WriteLine("方法2:標准冒泡算法排序");

    var count1 = 0;
    var count2 = 0;

    for (var i = 0; i < numbers.Length; i++)
    {
        for (var j = numbers.Length - 1; j > i; j--)
        {
            count1++;
            if (numbers[j] >= numbers[j - 1]) continue;

            count2++;
            var temp = numbers[j - 1];
            numbers[j-1] = numbers[j];
            numbers[j] = temp;
        }
    }

    Console.WriteLine($"結果:{string.Join(",", numbers)};循環次數:{count1};數據交換次數:{count2}");
}

運行一下,我們會發現,調整后的代碼循環的次數和效率跟第一版一模一樣,肯定的啊,因為中心思路沒變,只是把冒泡的方向倒換了一下而已。

下面,我們考慮這樣一個數組:[1,0,2,3,5,4],使用第二版的代碼:

第一次循環結束,數組結果為 [0,1,2,3,4,5],很神奇有沒有,我們不僅把最小的0冒泡到第一位,順帶的最后兩個元素也進行了排序。然后目測一下,數組已經是正序排列了,也就是說這是我們應該可以返回了有木有?

這也就是為什么要從尾部往前冒泡的原因。好了,可以優化的點出現了,接下來,請看第三版代碼

public void Sort3(int[] numbers)
{
    Console.WriteLine("方法2:優化版冒泡算法排序");

    var count1 = 0;
    var count2 = 0;

    // 剩下的數據是否需要繼續排序標志
    // 因為冒泡排序是從后端兩兩交換,所以在某種時間點上,后端數據可能已經是排列好的數據
    // 如1,0,2,3,4這種情況,第一次循環后,把0交換到最前,結果為0,1,2,3,4
    // 繼續循環1,2,3,4,如果沒有進行數據交換操作,說明已經是排序好的,就沒必要繼續了,直接跳出即可
    var continueFlag = true;

    for (var i = 0; i < numbers.Length && continueFlag; i++)
    {
        // 沒有發生數據交換,說明數據已經是排列好的,這時可以跳出循環了
        continueFlag = false;

        for (var j = numbers.Length - 1; j > i; j--)
        {
            count1++;

            if (numbers[j] < numbers[j - 1])
            {
                count2++;
                var temp = numbers[j - 1];
                numbers[j - 1] = numbers[j];
                numbers[j] = temp;

                // 一旦發生數據交換的操作,說明后面的數據並沒有排列好,這時需要繼續循環
                continueFlag = true;
            }
        }
    }

    Console.WriteLine($"結果:{string.Join(",", numbers)};循環次數:{count1};數據交換次數:{count2}");
}

下面,來進行一個測試:

var bubble = new BubbleSortSample();
var numbers = new int[] {3, 1, 2, 4, 6, 9, 5};
bubble.Sort1(numbers);

Console.WriteLine();

numbers = new int[] { 3, 1, 2, 4, 6, 9, 5 };
bubble.Sort2(numbers);

Console.WriteLine();

numbers = new int[] { 3, 1, 2, 4, 6, 9, 5 };
// 第一次 1,3,2,4,5,6,9,發生了交換,循環了6次
// 第二次 1,2,3,4,5,6,9,發生了交換,循環了5次
// 第三次 檢查了一圈,循環了4次,沒有發生交換,跳出
bubble.Sort3(numbers);

冒泡排序是一種比較基礎而且大家比較熟悉的算法,以前總是知其然而沒有深究過,網上很多例子也是如第一版代碼一樣簡單實現演示一下了事,卻不知原來這么基礎的算法也有這么多的門道在其中,所以說學海無涯...省略一萬五千字雞湯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM