邏輯之美(1)_冒泡排序

獻給我本身java

瞎扯

瞎扯是文章中能夠略過不讀的部分,固然你若欣賞個人文筆那另說;-) 過了很久,終於決定動筆寫寫算法了!是的若是你對文章標題感到困惑的話,其實就是寫算法的。動筆寫前,我想着給文章起個牛逼點的標題,思來想去,技術文章起太標題黨的標題怕是不妥。但也不能太平淡吧!估計太張揚太平淡的標題都會把讀者勸退。我初次想好的標題打出來是:算法之冒泡排序,看着太過無趣!多想一想,所謂算法,不就是用來描述邏輯的嘛,我想着能夠用文字把邏輯的美描述出來(吹了個大牛逼!),聯想到吳軍寫的那本《數學之美》,標題到手!算法能夠聊的東西太多了,咱們從最初級的排序算法——冒泡排序提及。算法

正文

冒泡排序應該是全部排序算法裏面最容易實現的。其實現思路總結起來很簡單,咱們以初級數據結構數組爲例說明(感謝數組):屢次遍歷一個整數數組,每次都從頭開始將數組元素兩兩比較(由於須要兩兩比較因此你須要設置兩個指針,用於數組元素兩兩比較,也用於元素值兩兩交換),若是第二個元素的值大於第一個元素的值就交換兩元素的值。不難見得,每遍歷完一次,數組中都會多出一個元素變得部分有序(每遍歷完一次,遍歷區間的最後一個數即爲本次遍歷的最大數,遍歷區間是遞減的,啥是遍歷區間後面會解釋到),冒泡排序其實就是個從部分有序總體有序的一個過程(咱們在本文假設數組的總體有序是指數組嚴格按照從小到大排列,既數組中前一個元素的值確定不大於它後面一個元素的值,別的有序方式本文所述內容一樣適用,你們發散下思惟便可),嗯這麼一說好像是種減而治之的策略,事實確實如此。我前面說的好像太書面!其實冒泡這個名字起的就很形象,你看咱們每遍歷一次數組,就要把當前遍歷區間內的最大數放到最後面(先讓最大的水泡冒出來),讓最大數冒個泡~直至大水泡冒完,咱們的數組達到總體有序!不難見得,咱們最多隻須要等於數組長度的比較次數就能徹底將數組排好序。數組

Tips:部分有序總體有序數據結構

設現有數組 [3, 5, 4, 2, 1, 0] ,咱們如今須要對其排個序使其變成徹底按值從小到大的排列的總體有序函數

對於如此簡短的數組,咱們可一眼看出其變成總體有序時,最後一個元素必然是5,這至關於使其變成post

[3, 4, 2, 1, 0, 5]優化

這時咱們會說此數組最後一個元素已達成部分有序,此時繼續爲此數組排序時能夠忽略最後一個元素,即部分有序的部分。是的,此時咱們要處理的問題減少了一個元素的規模。spa

咱們先來具象化捋一遍冒泡排序的邏輯:指針

設現有無序數組 a = [4, 5, 2, 3, 1, 0]
    其有序狀態應爲 a = [0, 1, 2, 3, 4, 5]
    咱們對其作下冒泡排序,具象展現以下:
數組下標:a[0]  a[1]  a[2]  a[3]  a[4]  a[5]
初始值: 4    5    2    3    1    0
第一次冒泡遍歷過程:
                 4   5   2   3   1   0 (a[0] < a[1] -> 不交換值)
                 ↑   ↑ (←咱們可愛的小指針)

                 4   5   2   3   1   0 (a[1] > a[2] -> 交換值)
                     ↑   ↑

                 4   2   5   3   1   0 (a[2] > a[3] -> 交換值)
                         ↑   ↑

                 4   2   3   5   1   0 (a[3] > a[4] -> 交換值)
                             ↑   ↑

                 4   2   3   1   5   0 (a[4] > a[5] -> 交換值)
                                 ↑   ↑

第一次冒泡遍歷結果:4   2   3   1   0   5 (本趟遍歷區間爲整個數組,
                                      -- 最大的泡泡已位於本趟遍歷區間最後面,
                                         既本數組最後一個元素已部分有序
                                         下次遍歷區間將不包括已部分有序的部分,後同)

第二次冒泡遍歷結果:2   3   1   0   4   5 (數組最後兩個元素已部分有序)
                                  ------
第三次冒泡遍歷結果:2   1   0   3   4   5 (數組最後三個元素已部分有序)
                              ----------
第四次冒泡遍歷結果:1   0   2   3   4   5 (數組最後四個元素已部分有序)
                          --------------
第五次冒泡遍歷結果:0   1   2   3   4   5 (數組最後五個元素已部分有序)
                      ------------------
第六次冒泡遍歷結果:0   1   2   3   4   5 (待遍歷區間就只剩一個元素了,
                  ----------------------- 還遍歷個雞兒!
                                          它確定就是當前數組中值最小的元素,
                                          數組已總體有序)
複製代碼

OK 邏輯分析完畢咱們開始擼代碼,先徹底按照上面的分析來個易讀的遞歸版本,用 Java 語言編寫:code

/** * @see: 冒泡排序的遞歸實現 * @param array: 待排序數組,咱們採用原地排序 * @param orderRange: 數組當前已部分有序的元素個數,初始調用值應爲0 */
    public static void sortBubble(int[] array, int orderRange){
        //遞歸結束條件
        if (orderRange >= array.length - 1){
            return;
        }
        //指針邊界,思考爲啥要減2,想改爲1改下下面循環的判斷條件便可,注意邊界確定還要減去已部分有序的元素個數
        int bound = array.length - 2 - orderRange;
        //這樣一次循環遍歷就完成一次冒泡
        for (int i = 0; i <= bound; i ++){
            //判斷是否須要交換兩個元素的值
            if (array[i] > array[i + 1]){
                //不一樣下標數值交換
                int mid = array[i];
                array[i] = array[i + 1];
                array[i + 1] = mid;
            }
        }
        //遞歸開始下次冒泡
        sortBubble(array, orderRange + 1);
    }
複製代碼

附 Kotlin 實現版本(做者最近癡迷於 Kotlin!讀者可自行略過):

``

/** * @see: 冒泡排序的遞歸實現 * @param array: 待排序數組,咱們採用原地排序 * @param orderRange: 數組當前已部分有序的元素個數,初始調用值應爲0 */
    fun sortBubble(array: IntArray, orderRange: Int) {
        //遞歸結束條件
        if (orderRange >= array.size - 1) {
            return
        }
        //指針邊界,思考爲啥要減2,想改爲1改下下面循環的判斷條件便可,注意邊界確定還要減去已部分有序的元素個數
        val bound = array.size - 2 - orderRange
        //這樣一次循環遍歷就完成一次冒泡
        for (i in 0..bound) {
            //判斷是否須要交換兩個元素的值
            if (array[i] > array[i + 1]) {
                //不一樣下標數值交換
                val mid = array[i]
                array[i] = array[i + 1]
                array[i + 1] = mid
            }
        }
        //遞歸開始下次冒泡
        sortBubble(array, orderRange + 1)
    }
複製代碼

這樣實現的代碼看着比較囉嗦,卻十分易讀,易於理解。到這裏,你應該已經徹底 get 到冒泡排序其算法是怎麼回事了~

咱們在實際生產環境中寫排序算法確定不能用遞歸,在 Java 中遞歸的函數調用棧太深會致開銷甚大,上面主要是爲便於理解來的初級版本,咱們來優化成非遞歸版本,即雙層嵌套版本:

/** * @see: 冒泡排序的非遞歸實現 * @param array: 待排序數組,咱們採用原地排序 */
    public static void sortBubble(int[] array){
        for (int bound = array.length - 1; bound > 0; bound --){
            //這樣一次循環遍歷就完成一次冒泡,使得數組部分有序
            for (int i = 0; i < bound; i ++){
                //判斷是否須要交換兩個元素的值
                if (array[i] > array[i + 1]){
                    //不一樣下標數值交換
                    int mid = array[i];
                    array[i] = array[i + 1];
                    array[i + 1] = mid;
                }
            }
        }
    }
複製代碼

附 Kotlin 實現版本:

/** * @see: 冒泡排序的非遞歸實現 * @param array: 待排序數組,咱們採用原地排序 */
    fun sortBubble(array: IntArray) {
        for (bound in array.size - 1 downTo 1) {
            //這樣一次循環遍歷就完成一次冒泡,使得數組部分有序
            for (i in 0 until bound) {
                //判斷是否須要交換兩個元素的值
                if (array[i] > array[i + 1]) {
                    //不一樣下標數值交換
                    val mid = array[i]
                    array[i] = array[i + 1]
                    array[i + 1] = mid
                }
            }
        }
    }
複製代碼

固然,你若對位運算有所瞭解,其實交換兩個變量的值是能夠不須要中間變量的:

/** * @see: 冒泡排序的非遞歸實現 * @param array: 待排序數組,咱們採用原地排序 */
    public static void sortBubble1(int[] array){
        for (int bound = array.length - 1; bound > 0; bound --){
            //這樣一次循環遍歷就完成一次冒泡,使得數組部分有序
            for (int i = 0; i < bound; i ++){
                //判斷是否須要交換兩個元素的值
                if (array[i] > array[i + 1]){
                    //不一樣下標數值交換,省去中間變量
                    array[i] ^= array[i + 1];
                    array[i + 1] ^= array[i];
                    array[i] ^= array[i + 1];
                }
            }
        }
    }
複製代碼

附 Kotlin 實現版本:

/** * @see: 冒泡排序的非遞歸實現 * @param array: 待排序數組,咱們採用原地排序 */
    fun sortBubble1(array: IntArray) {
        for (bound in array.size - 1 downTo 1) {
            //這樣一次循環遍歷就完成一次冒泡,使得數組部分有序
            for (i in 0 until bound) {
                //判斷是否須要交換兩個元素的值
                if (array[i] > array[i + 1]) {
                    //不一樣下標數值交換,省去中間變量
                    array[i] = array[i] xor array[i + 1]
                    array[i + 1] = array[i + 1] xor array[i]
                    array[i] = array[i] xor array[i + 1]
                }
            }
        }
    }
複製代碼

OK 代碼咱們就擼到這裏,總結一下冒泡排序算法。估計看到雙層嵌套循環你就能猜到冒泡排序算法的平均時間複雜度爲:O(n^2)

事實確實如此,至於更具體的數值比較次數和交換次數分析,留給讀者。

除去原數組自己所須要的存儲空間外,冒泡排序算法實現所需額外輔助空間爲:O(1)

不一樣時間複雜度算法的增加數量級函數:

不一樣時間複雜度算法的增加數量級函數

尾巴

以上代碼我原本都還想附上 C++ 版本的!當時在學校我 C++ 學的還挺不錯的,可工做後主力用 Java,C++基本上忘光了!

肥宅大哭

計劃後期補上!

後續文章考慮不貼 Koltin 代碼了!

看完你可能以爲文章標題挺唬人的!不就是聊聊最基礎的算法嘛,吹什麼邏輯之美!

歡迎上當~

冒泡排序是全部排序算法裏面最簡單的,下篇咱們聊聊一樣很簡單的選擇排序

完。

相關文章
相關標籤/搜索