一看就懂的冒泡排序和【3】步深度優化法

如需轉載,請標明地址

前言:各位小夥伴們,冒泡排序做爲我入門編程第一個遇到的算法,對我來講意義非凡。今天閒來從新拾起了這個算法,發現它居然還有這麼大的優化空間,驚訝。那咱們就來優化一下它吧!寫這篇文章呢主要是想和在座的各位小夥伴分享一下個人優化歷程,二來還能夠方便之後複習。廢話很少說。咱們直接開始吧!java


相比你們對冒泡排序法仍是不陌生的,若是你是剛剛接觸編程也不要緊,請看我慢慢給你解答!

基礎比較好的小夥伴能夠直接略過算法

什麼是冒泡排序?


冒泡排序(Bubble Sort)是一種較簡單的排序算法。

經過比較兩個相鄰數組元素來達到由大(xiao)到小(da)排序數組的目的

我這麼說是否是能明白一點呢?不明白也不要緊,就讓咱們一塊兒來看代碼吧!編程

int[] array = {
    9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
複製代碼

這是一個0-9的倒序排列的數組,咱們經過相鄰元素的下標比對而後互換來完成從小到大的排序,如圖:數組

這樣咱們就完成了將9放到了數組的最後。完成了一次排序。bash


怎麼樣,你是否是能明白了呢?

說到這裏,那咱們如何用代碼來實現呢?性能

int[] array = {
    9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
// 一次遍歷,將相對最大的數放到數組底部
for (int j = 0; j < array.length - 1; j++) {
    if (array[j] > array[j + 1]) {
        int max = array[j];
        array[j] = array[j + 1];
        array[j + 1] = max;
    }
}
複製代碼

輸出的結果: [8, 7, 6, 5, 4, 3, 2, 1, 0, 9]測試


那怎麼完成全部元素的排序呢?

那就太好辦了!再加一個循環吧!優化

int[] array = {
    9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
int max = 0;

// 一次遍歷,在倒序狀況下最少遍歷的次數
for (int i = 0; i < arrays.length - 1; i++) {
    // 二次遍歷,將相對最大的數放到數組底部
    for (int j = 0; j < array.length - 1; j++) {
        if (array[j] > array[j + 1]) {
            max = array[j];
            array[j] = array[j + 1];
            array[j + 1] = max;
        }
    }
}
複製代碼

輸出結果: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]spa


作到這裏,咱們發現了,這樣的寫法並不完美,有不少紕漏。大大影響了程序的性能。那咱們應該怎麼去優化呢?

咱們的目的:3d

  • 增長循環效率
  • 減小無用的循環遍歷和判斷

最有用的辦法就是觀察算法的有效遍歷數和實際遍歷數


第一步優化

那咱們就想辦法先爲程序減小一些循環吧!

我發如今執行二次遍歷時,程序越運行到後面,所作的排序就越少,由於數組後面的元素都已排序完成,無需再進行循環判斷

這樣一想,咱們的優化方案就有了!

一次遍歷的計數(i)就至關於咱們數組已經排好元素的個數

減去(i),就能夠減小循環次數

int[] array = {
    9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
// 程序有效運行的次數
int runCount = 0;
// 一共遍歷的次數
int allCount = 0;
int max = 0;

// 一次遍歷,在倒序狀況下最少遍歷的次數
for (int i = 0; i < array.length - 1; i++) {
    // 二次遍歷,將相對最大的數放到數組底部
    for (int j = 0; j < array.length - 1 - i; j++) {
            if (array[j] > array[j + 1]) {
                max = array[j];
                array[j] = array[j + 1];
                array[j + 1] = max;
                runCount += 1;
        }
        allCount += 1;
    }
}
System.out.println("runCount = " + runCount);
System.out.println("allCount = " + allCount);
System.out.println(Arrays.toString(array));
複製代碼

輸出結果:

runCount = 45
allCount = 45
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼

從結果來看,有效遍歷和實際遍歷次數相同。

可是這是在極端狀況下(徹底倒序),咱們拿到的數組大多數狀況都是無序散亂的。這樣的優化明顯不能知足咱們的要求。這又爲第二次優化提供了思路...


第二步優化

每每散亂的數組實際所需的遍歷次數是遠小於極端狀況(徹底倒序)的,然而咱們程序仍是會進行循環遍歷.

那咱們不如作個判斷,判斷它是否須要進行實際遍歷,若是不須要了。那數組確定是排序完成了!那咱們就能夠跳出循環了。

這樣一想,咱們的優化方案又有了!

咱們用無序數組進行測試

int[] array = {
    3, 6, 2, 7, 9, 5, 0, 1, 4, 8
};
// 程序有效運行的次數
int runCount = 0;
// 一共遍歷的次數
int allCount = 0;
int max = 0;
// flag判斷排序是否完成 true-完成;false-未完成
boolean flag;

// 一次遍歷,在倒序狀況下最少遍歷的次數
for (int i = 0; i < array.length - 1; i++) {
    // 每次循環重置flag爲true
    flag = true;
    // 二次遍歷,將相對最大的數放到數組底部
    for (int j = 0; j < array.length - 1 - i; j++) {
            if (array[j] > array[j + 1]) {
                max = array[j];
                array[j] = array[j + 1];
                array[j + 1] = max;
                runCount += 1;
                // 進入循環表示數組未排序完成,需再次循環
                flag = false;
        }
        allCount += 1;
    }
    // 若是已經完成排序,則跳出循環
    if (flag) {
        break;
    }
    
}
System.out.println("runCount = " + runCount);
System.out.println("allCount = " + allCount);
System.out.println(Arrays.toString(array));
複製代碼

輸出結果:

runCount = 22
allCount = 42
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼

未加判斷輸出結果:

runCount = 22
allCount = 45
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼

從結果能夠看出來,比未加判斷的實際遍歷次數少了3次。

可是優化到此爲止好像仍是缺了點什麼,若是數組天生有一部分就是無需排序的,那咱們又會浪費不少次的循環,這麼一想,第三步優化就有了方向。


第三步優化

如圖:

後面的 5 6 7 8 9 原本就是排序完成的,那按照咱們的代碼還要去對後面的代碼進行循環遍歷,那樣是很不科學的!

這樣一想,咱們的優化方案就完美了!

我用幾個變量來動態記錄數組所需遍歷的次數就能夠解決問題了。

int[] array = {
    3, 6, 2, 7, 9, 5, 0, 1, 4, 8
};
// 程序有效運行的次數
int runCount = 0;
// 一共遍歷的次數
int allCount = 0;
int max = 0;
// flag判斷排序是否完成 true-完成;false-未完成
boolean flag;
// 無序數組循環邊界,默認爲數組長度array.length - 1
int sortBorder = array.length - 1;
// 記錄數組最後進行排序的位置
int lastChange = 0;

// 一次遍歷,在倒序狀況下最少遍歷的次數
for (int i = 0; i < array.length - 1; i++) {
    // 每次循環重置flag爲true
    flag = true;
    // 二次遍歷,將相對最大的數放到數組底部
    for (int j = 0; j < sortBorder; j++) {
            if (array[j] > array[j + 1]) {
                max = array[j];
                array[j] = array[j + 1];
                array[j + 1] = max;
                runCount += 1;
                // 進入循環表示數組未排序完成,需再次循環
                flag = false;
                // 記錄數組最後進行排序的位置
                lastChange = j;
        }
        allCount += 1;
    }
    // 動態設置無序數組循環邊界
    sortBorder = lastChange;
    // 若是已經完成排序,則跳出循環
    if (flag) {
        break;
    }
}
System.out.println("runCount = " + runCount);
System.out.println("allCount = " + allCount);
System.out.println(Arrays.toString(array));
複製代碼

輸出結果:

runCount = 22
allCount = 35
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼

優化到這一步咱們基本的需求就已經完成了,有效遍歷和實際遍歷次數已經至關接近了。

有的小夥伴會問了:怎麼仍是多出來 13 次啊?

我以爲在目前看來多於的次數對於有效遍歷提供了必定的幫助,因此並非徹底無效的。

不懂的小夥伴能夠複製代碼進行 bebug 也能夠直接問我。可是不要中止思考哦。說不定你就找出更好的優化方案了呢!

最後仍是感謝各位小夥伴可以看到最後。文章有什麼出錯的地方歡迎指出改正。

相關文章
相關標籤/搜索