1. 程式人生 > >圖解氣泡排序及演算法優化(Java實現)

圖解氣泡排序及演算法優化(Java實現)

# 冒牌排序 ## 基本思想 **定義**:氣泡排序的英文是bubblesort,它是一種基礎的交換排序 **原理**:每次比較兩個相鄰的元素,將較大的元素交換至右端 (升序排序) **思路**:相鄰的元素兩兩比較,當一個元素大於右側相鄰元素時,交換它們的位置;當一個元素小於或等於右側相鄰元素時,位置不變 **案例分析**: 1、初始的無序數列 `{5,8,6,3,9,2,1,7}`,希望對其升序排序 2、按照思路分析: ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/97c03dd6ec5d4ea88ede4c4a4fc29005~tplv-k3u1fbpfcp-zoom-1.image) 在經過第一輪交換後,最大的數 9 冒泡到了最右邊 ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/efcd1e1b7c074a45ae960ccb8be82e63~tplv-k3u1fbpfcp-zoom-1.image) 到此為止,所有元素都是有序的了,這就是氣泡排序的整體思路。 3、氣泡排序是一種穩定排序,值相等的元素並不會打亂原本的順序。由於該排序演算法的每一輪都要遍歷所有元素,總共遍歷(元素數量-1)輪,所以平均時間複雜度是O(n2)。 ## **程式碼實現** ### 第 1 版程式碼 ```java public static void bubbleSort(int[] arr){ if (arr == null || arr.length == 0) return; for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 -i; j++) { int tmp = 0; //升序排序>,降序排序< if (arr[j] > arr[j + 1]){ tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp; } } } } ``` 使用雙迴圈進行排序。外部迴圈控制所有的回合,內部迴圈實現每一輪的冒泡處理,先進行元素比較,再進行元素交換。 ### 第 2 版程式碼 仍以無序數列 `{5,8,6,3,9,2,1,7}`為例,我們發現在第 6 輪的時候,數列已經是有序了,但氣泡排序仍然進行了第7輪,可以做一個小優化,在外層迴圈設定一個哨兵標記`isSorted`,預設有序,內層迴圈如果發生交換,則仍為無序 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e72626dde6d4e7fb5875302c167237b~tplv-k3u1fbpfcp-zoom-1.image) ```java public static void bubbleSort(int[] arr){ if (arr == null || arr.length == 0) return; for (int i = 0; i < arr.length - 1; i++) { //是否已經有序的標記,預設有序 boolean isSorted = true; for (int j = 0; j < arr.length - 1 -i; j++) { int tmp = 0; //升序排序>,降序排序< if (arr[j] > arr[j + 1]){ tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp; //發生元素交換,序列仍是無序狀態 isSorted = false; } } if (isSorted){ break; } } } ``` 以`isSorted`作為標記。如果在本輪排序中,元素有交換,則說明數列無序;如果沒有元素交換,則說明數列已然有序,然後直接跳出大迴圈。 ### 第 3 版程式碼 以新的無序數列 `{3,4,2,1,6,7,8,9}`為例,發現前半部分是無序的,而後半部分[6 ,7 ,8 ,9]是有序區間 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e050b3791eff43628e947138520976eb~tplv-k3u1fbpfcp-zoom-1.image) 如果以上面的第2版程式碼執行,會發現只有前半部分的比較是有意義的,而後半部分的有序區間的比較是無意義的 ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8f49c9c3793c46de903b06b81b126070~tplv-k3u1fbpfcp-zoom-1.image) 怎麼避免這種情況?那麼可以在每一輪的排序後,記錄下來最後一次元素交換的位置,該位置即為無序數列的邊界,再往後就是有序區 ```java public static void bubbleSort(int[] arr){ if (arr == null || arr.length == 0) return; //記錄記錄下來最後一次元素交換的位置 int lastExchangeIndex = 0; //無序數列的邊界,每次比較只需要比到這裡為止 int sortBorder = arr.length-1; for (int i = 0; i < arr.length - 1; i++) { //是否已經有序的標記,預設有序 boolean isSorted = true; for (int j = 0; j < sortBorder; j++) { int tmp = 0; //升序排序>,降序排序< if (arr[j] > arr[j + 1]){ tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp; //發生元素交換,序列仍是無序狀態 isSorted = false; //更新為最後一次交換元素的位置 lastExchangeIndex = j; } } //更新無序數列的邊界 sortBorder = lastExchangeIndex; if (isSorted){ break; } } } ``` 在第3版程式碼中,sortBorder就是無序數列的邊界。在每一輪排序過程中,處於sortBorder之後的元素就不需要再進行比較了,肯定是有序的 ## 演算法升級 ### 分析 冒泡演算法的每一輪都是從左到右來比較元素,進行單向的位置交換的,是單向的 以新的無序數列 `{2,3,4,5,6,7,8,1}`為例,按照氣泡排序的演算法,排序過程如下: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e7af7f9802fb4c2687e6b3900f5cacbb~tplv-k3u1fbpfcp-zoom-1.image) 事實上,前面的[2,3,4,5,6,7,8]已經是有序了,只有元素1的位置不正確,卻要進行7輪交換。可以將演算法從單向交換改為雙向交換,排序過程就像鐘擺一樣,第1輪從左到右,第2輪從右到左,第3輪再從左到右……,這就是`雞尾酒排序`. ### 雞尾酒排序 圖解雞尾酒排序: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3e1668130f944d5180d3085d333c3755~tplv-k3u1fbpfcp-zoom-1.image) 經過2輪交換(雖然實際上已經有序,但是流程並沒有結束),進入第3輪交換從左到右進行,1和2比較,位置不變;2和3比較,位置不變;3和4比較,位置不變……6和7比較,位置不變。 沒有元素位置進行交換,證明已經有序,排序結束。 ```java public static void cockTailSort(int[] arr){ if (arr == null || arr.length == 0) return; // 記錄右側最後一次交換的位置 int lastRightExchangeIndex = 0; // 記錄左側最後一次交換的位置 int lastLeftExchangeIndex = 0; // 無序數列的右邊界,每次比較只需要比到這裡為止 int rightSortBorder = arr.length - 1; // 無序數列的左邊界,每次比較只需要比到這裡為止 int leftSortBorder = 0; //i設定為1,代表從第1輪開始 for (int i = 1; i < arr.length; i++) { boolean isSorted = true; //奇數,從左到右 if (i % 2 != 0) { for (int j = leftSortBorder; j < rightSortBorder; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; //發生元素交換,序列仍是無序狀態 isSorted = false; //更新為右側最後一次交換元素的位置 lastRightExchangeIndex = j; } } } else { //偶數,從右到左 for (int j = rightSortBorder; j > leftSortBorder; j--) { if (arr[j] < arr[j - 1]) { int temp = arr[j]; arr[j] = arr[j - 1]; arr[j - 1] = temp; //發生元素交換,序列仍是無序狀態 isSorted = false; //更新為左側最後一次交換元素的位置 lastLeftExchangeIndex = j; } } } //更新無序數列的左邊界 leftSortBorder = lastLeftExchangeIndex; //更新無序數列的右邊界 rightSortBorder = lastRightExchangeIndex; if (isSorted) { break; } } } ``` **優缺點**:雞尾酒排序的優點是能夠在特定條件下,減少排序的回合數;而缺點也很明顯,就是程式碼量幾乎增加了1倍。 **應用場景:**無序數列中大部分元素已經有序 **參考圖書:**《漫畫演算法—小灰的演算法之旅》 歡迎關注[我的掘金](https://juejin.im/user/115194391930484