前言:各位小夥伴們,冒泡排序做爲我入門編程第一個遇到的算法,對我來講意義非凡。今天閒來從新拾起了這個算法,發現它居然還有這麼大的優化空間,驚訝。那咱們就來優化一下它吧!寫這篇文章呢主要是想和在座的各位小夥伴分享一下個人優化歷程,二來還能夠方便之後複習。廢話很少說。咱們直接開始吧!java
基礎比較好的小夥伴能夠直接略過算法
我這麼說是否是能明白一點呢?不明白也不要緊,就讓咱們一塊兒來看代碼吧!編程
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 也能夠直接問我。可是不要中止思考哦。說不定你就找出更好的優化方案了呢!