Java中的氣泡排序演算法例項
氣泡排序是一種奇特的演算法,它既是最流行的排序演算法之一也是效能最差的排序演算法之一。氣泡排序的平均案例效能為O(n ^ 2),這意味著隨著陣列大小的增長,排序該陣列所需的時間將增加二次方。由於這個原因,在生產程式碼中不使用氣泡排序,而是優先選擇快速排序和合並排序。實際上,Java自己的Arrays.sort()方法,是在Java中對陣列進行排序的最簡單方法,它還使用兩個pivot快速排序來對基元陣列進行排序,用穩定的合併排序演算法對物件陣列進行排序。
這種演算法效能低下的原因是過度比較和交換,因為它將陣列的每個元素與另一個元素進行比較,如果在右側則交換。
由於它的二次效能,氣泡排序最適合於小的,幾乎排序的列表,例如{1,2,4,3,5},只需要進行一次交換。具有諷刺意味的是,氣泡排序的最佳效能,即O(n)擊敗了快速排序的O(NlogN)的最佳效能。
有人可能會爭辯說,為什麼要教一個性能那麼差的演算法,為什麼不教插入或選擇排序,它和氣泡排序一樣簡單,而且效能更好。恕我直言,演算法的簡易性不僅取決於演算法本身,還取決於程式設計師。
許多程式設計師會發現插入排序比氣泡排序更容易,但是會有很多人會發現更容易記住的氣泡排序。儘管他們中的許多人在現實生活中不知不覺地使用插入排序,例如在手中整理撲克牌。
學習這種排序演算法的另一個原因是用於比較分析,如何改進演算法,如何為相同的問題提出不同的演算法。簡而言之,儘管存在各種缺點,但氣泡排序仍然是最流行的演算法。
在本教程中,我們將學習氣泡排序的工作原理,氣泡排序演算法的複雜性和效能,Java中的實現和原始碼以及氣泡排序的分步示例。
氣泡排序演算法的工作原理
就像氣泡從水中出現一樣,在氣泡排序中最小或最大數字,取決於您是按升序還是降序排序陣列,氣泡朝著陣列的開始或結束冒出。 我們至少需要n次傳遞來對陣列進行完全排序,並且在每個傳遞的末尾,一個元素被排序到其正確的位置。您可以從陣列中提取第一個元素,並開始將其與其他元素進行比較,交換小於您要比較的數字的元素。您可以從開始或結束進行比較,正如我們在氣泡排序示例中從結束對元素進行比較一樣。讓我們看一個循序漸進的例子, 讓我們看一個循序漸進的例子,使用氣泡排序對陣列進行排序。
在這個陣列中,我們從索引0(即5)開始,並從開始到結束對元素進行比較。因此,我們比較的第一個元素是1,因為5大於1,所以我們交換它們(因為按升序排序的陣列在末尾將有更大的數字)。接下來我們比較5到6,這裡沒有交換,因為6大於5,並且它在高於5的索引上。現在我們將6與2進行比較,再次需要交換以將6移向末尾。在這一過程的末尾,6到達(氣泡上升)陣列的頂部。在下一個迭代中,5將按其位置排序,在n次迭代之後,所有元素都將排序。由於我們將每個元素與另一個元素進行比較,因此需要兩個for迴圈,這會導致o(n^2)的複雜性。
氣泡排序演算法流程圖
理解演算法的另一個很酷的方法是繪製它的流程圖。它將遍歷迴圈中的每個迭代以及在演算法執行期間如何做出決策。這是我們的氣泡排序演算法的流程圖,它補充了我們對這種排序演算法的實現。
這裡我們有整數陣列{9,7,3,6,2},從四個變數i,j,temp和陣列長度開始,它儲存在變數n中。我們有兩個for迴圈,外迴圈執行從1到n-1。我們的內迴圈從n-1到i。許多程式設計師在這裡犯了錯誤,如果您使用第二個元素開始外部迴圈,而不是確保在內部迴圈上使用j>=i條件,或者如果您使用第一個元素(例如i=0)開始,請確保使用j>i 來避arrayindexoutofbound異常。現在我們比較每個元素並交換它們以將更小的元素移向陣列的前面。如我所說,根據您的導航方向,第一遍中最大的元素將按最高索引排序,或者最小的元素將按最低索引排序。在這種情況下,在第一次通過後,將對最小的數字進行排序。這個迴圈一直執行到j>=i,然後結束,i 變成i+1。整個過程將重複,直到外部迴圈完成,然後對陣列進行排序。在流程圖中,菱形框用於決策,相當於程式碼中的if-else語句。您可以在這裡看到決策框在內部迴圈中,這意味著我們在每個迭代中都要進行N次比較,總計NxN個比較。
氣泡排序演算法的複雜性和效能
正如我之前所說的,與其他排序演算法(如快速排序,合併排序或shell排序)相比,氣泡排序表現不佳。這些演算法的平均情況複雜度為O(NLogN),而氣泡排序平均情況複雜度為O(n ^ 2)。具有諷刺意味的是,在最佳情況下,氣泡排序比具有O(n)效能的快速排序更好。氣泡排序比quicksort或mergesort慢三倍,即使n = 100,但它更容易實現和記憶。這裡是氣泡排序效能和複雜性的摘要:
氣泡排序最差情況效能O(n ^ 2)
氣泡排序最佳情況效能O(n)
氣泡排序平均情況效能O(n ^ 2)
您可以進一步探索插入排序和選擇排序,它也會以類似的時間複雜度進行排序。您不僅可以使用氣泡排序,還可以使用ArrayList或任何其他集合類對陣列進行排序。雖然你真的應該使用Arrays.sort()或Collections.sort()來實現這些目的。
Java中的氣泡排序實現
這是使用Java實現氣泡排序演算法的程式。不要驚訝於匯入java.util.Array,這裡我們沒有使用它的排序方法,而是使用它以可讀格式列印陣列。我已經建立了一個交換函式來交換數字和提高程式碼的可讀性,如果你不喜歡,可以在內部迴圈語句if內部的swap方法中內聯程式碼。雖然我已經使用main方法進行測試,但為了更好地展示,我建議你為冒泡實現編寫一些單元測試用例。
import java.util.Arrays; /** * Java program to implement bubble sort algorithm and sort integer array using * that method. * * @author Javin Paul */<font> <b>public</b> <b>class</b> BubbleSort{ <b>public</b> <b>static</b> <b>void</b> main(String args[]) { bubbleSort(<b>new</b> <b>int</b>[] { 20, 12, 45, 19, 91, 55 }); bubbleSort(<b>new</b> <b>int</b>[] { -1, 0, 1 }); bubbleSort(<b>new</b> <b>int</b>[] { -3, -9, -2, -1 }); } </font><font><i>/* * This method sort the integer array using bubble sort algorithm */</i></font><font> <b>public</b> <b>static</b> <b>void</b> bubbleSort(<b>int</b>[] numbers) { System.out.printf(</font><font>"Unsorted array in Java :%s %n"</font><font>, Arrays.toString(numbers)); <b>for</b> (<b>int</b> i = 0; i < numbers.length; i++) { <b>for</b> (<b>int</b> j = numbers.length -1; j > i; j--) { <b>if</b> (numbers[j] < numbers[j - 1]) { swap(numbers, j, j-1); } } } System.out.printf(</font><font>"Sorted Array using Bubble sort algorithm :%s %n"</font><font>, Arrays.toString(numbers)); } </font><font><i>/* * Utility method to swap two numbers in array */</i></font><font> <b>public</b> <b>static</b> <b>void</b> swap(<b>int</b>[] array, <b>int</b> from, <b>int</b> to){ <b>int</b> temp = array[from]; array[from] = array[to]; array[to] = temp; } } Output Unsorted array in Java : [20, 12, 45, 19, 91, 55] Sorted Array using Bubble sort algorithm : [12, 19, 20, 45, 55, 91] Unsorted array in Java : [-1, 0, 1] Sorted Array using Bubble sort algorithm : [-1, 0, 1] Unsorted array in Java : [-3, -9, -2, -1] Sorted Array using Bubble sort algorithm : [-9, -3, -2, -1] </font>
如何改進氣泡排序演算法
在面試中,一個普遍的後續問題是如何改進特定演算法,而氣泡排序也正如此。為了改進任何演算法,您必須瞭解該演算法的每個步驟是如何工作的,然後只有您能夠發現程式碼中的任何缺陷。如果按照本教程,您將發現數組是通過將元素移動到其正確位置來排序的。在最壞的情況下,如果陣列是反向排序,那麼我們需要移動每個元素,這將需要n-1次過程,每次過程有n-1次比較和n-1次交換,但如果陣列已經排序,最佳情況是:現有的氣泡排序方法仍將採用n-1個過程,相同的比較次數,但沒有交換。如果仔細觀察,你會發現在一次通過陣列後,最大的元素將移動到陣列的末尾,許多其他元素也會移動到正確的位置。通過利用此屬性,可以推斷在傳遞過程中,如果沒有一對連續的條目順序不對,那麼陣列將被排序。我們當前的演算法沒有利用這個屬性。如果我們跟蹤交換,那麼我們可以決定是否需要對陣列進行額外的迭代。這是Bubble Sort演算法的改進版本,當陣列已經排序時,在最佳情況下只需要1次迭代和n-1次比較。與我們現有的N-1次傳遞方法相比,這也將改善Bubble sort的平均案例效能。
<font><i>/* * An improved version of Bubble Sort algorithm, which will only do * 1 pass and n-1 comparison if array is already sorted. */</i></font><font> <b>public</b> <b>static</b> <b>void</b> bubbleSortImproved(<b>int</b>[] number) { <b>boolean</b> swapped = <b>true</b>; <b>int</b> last = number.length - 2; </font><font><i>// only continue if swapping of number has occurred</i></font><font> <b>while</b> (swapped) { swapped = false; <b>for</b> (<b>int</b> i = 0; i <= last; i++) { <b>if</b> (number[i] > number[i + 1]) { </font><font><i>// pair is out of order, swap them</i></font><font> swap(number, i, i + 1); swapped = <b>true</b>; </font><font><i>// swapping occurred</i></font><font> } } </font><font><i>// after each pass largest element moved to end of array</i></font><font> last--; } } </font>
現在讓我們用兩個輸入測試這個方法,一個是陣列排序(最佳情況),另一個是隻有一對無序。如果我們將int陣列{10,20,30,40,50,60}傳遞給這個方法,最初將在迴圈中進入並使swapped = false。然後它將進入迴圈。當i = 0時,它會將數字 與數字[i + 1]進行比較,即10到20,並檢查是否10> 20,因為不大於,它就不會進入內部,並且不會發生交換。當i=1時,將再次比較20>30不交換,下一次當i=2, 30>40為假,不再交換,下一次i=3,40>50為假,所以不交換。現在最後一對比較i=4,它將再次比較50>60,這是錯誤的,所以如果阻止不進行交換,控制元件將不會進入。因為交換始終為假,並且在迴圈再次進行時控制元件不會進入。所以你知道你的陣列在一次傳遞之後就被排序了。
現在考慮另一個例子,其中只有一對亂序,假設字串陣列名= {“Ada”,“C ++”,“Lisp”,“Java”,“Scala”},這裡只有一對亂序例如“Lisp”應該出現在“Java”之後。讓我們看看我們改進的氣泡排序演算法如何在這裡工作。在第一遍中,比較將繼續而不進行交換,直到我們比較“Lisp”和“Java”,這裡“Lisp”.compareTo(“Java”)> 0將變為true並且將發生交換,這意味著Java將轉到Lisp位置,Lisp將佔據Java的位置。這將使boolean變數swapped = true,現在在這個傳遞的最後比較中,我們將“Lisp”與“Scala”進行比較,並且再次沒有交換。現在我們將最後一個索引減少1,因為Scala在最後位置排序,不會進一步參與。但現在交換的變數是真的,所以控制元件將再次進入while迴圈和for迴圈,但這次不會進行交換,所以不會進行另一次傳遞。與早期實現的N-1次傳遞相比,我們的陣列現在只進行了兩次傳遞。在平均情況下,這種氣泡排序實現要比選擇排序演算法好得多,甚至表現更好,因為現在排序與元素總數不成比例,但只與亂序對的數量成比例。
順便說一下,使用氣泡排序對String陣列進行排序,需要過載BubbleSortImproved()方法來接受String [],還需要使用compareTo()方法按字典順序比較兩個String物件。這是使用氣泡排序對String陣列進行排序的Java程式:
<b>import</b> java.util.Arrays; <b>class</b> BubbleSortImproved { <b>public</b> <b>static</b> <b>void</b> main(String args[]) { String[] test = {<font>"Ada"</font><font>, </font><font>"C++"</font><font>, </font><font>"Lisp"</font><font>, </font><font>"Java"</font><font>, </font><font>"Scala"</font><font>}; System.out.println(</font><font>"Before Sorting : "</font><font> + Arrays.toString(test)); bubbleSortImproved(test); System.out.println(</font><font>"After Sorting : "</font><font> + Arrays.toString(test)); } </font><font><i>/* * An improved implementation of Bubble Sort algorithm, which will only do * 1 pass and n-1 comparison if array is already sorted. */</i></font><font> <b>public</b> <b>static</b> <b>void</b> bubbleSortImproved(String[] names) { <b>boolean</b> swapped = <b>true</b>; <b>int</b> last = names.length - 2; </font><font><i>// only continue if swapping of number has occurred</i></font><font> <b>while</b> (swapped) { swapped = false; <b>for</b> (<b>int</b> i = 0; i <= last; i++) { <b>if</b> (names[i].compareTo(names[i + 1]) > 0) { </font><font><i>// pair is out of order, swap them</i></font><font> swap(names, i, i + 1); swapped = <b>true</b>; </font><font><i>// swapping occurred</i></font><font> } } </font><font><i>// after each pass largest element moved to end of array</i></font><font> last--; } } <b>public</b> <b>static</b> <b>void</b> swap(String[] names, <b>int</b> fromIdx, <b>int</b> toIdx) { String temp = names[fromIdx]; </font><font><i>// exchange</i></font><font> names[fromIdx] = names[toIdx]; names[toIdx] = temp; } } Output: Before Sorting : [Ada, C++, Lisp, Java, Scala] After Sorting : [Ada, C++, Java, Lisp, Scala] </font>
選擇排序vs氣泡排序 哪個更好?
儘管在最壞的情況下,選擇排序和氣泡排序都具有O(n^2)的複雜性。平均而言,我們希望氣泡排序比選擇排序執行得更好,因為氣泡排序會比選擇排序更快完成排序,這是因為在相同數量的比較中有更多的資料移動,因為我們在氣泡排序上成對比較元素。如果我們使用改進的氣泡排序然後布林測試不進入while迴圈,當陣列被排序時也將有所幫助。如我所說,氣泡排序的最壞情況發生在原始陣列按降序排列時,而在最佳情況下,如果原始陣列已排序,氣泡排序將只執行一次,而選擇排序將執行n-1次。考慮到這一點,我認為氣泡排序比選擇排序平均要好。
這就是Java中的氣泡排序問題。我們已經瞭解了氣泡排序演算法的工作原理,以及如何在Java中實現它。它是最簡單的排序演算法之一,而且很容易記住,但是除了學術和資料結構以及演算法培訓課程之外,它沒有任何實際用途。最糟糕的情況是,效能是二次的,這意味著它不適合大型陣列或列表。如果必須使用氣泡排序,它最適合於已經排序的小型陣列,在這種情況下,它進行非常少的交換,其效能可能是O(N)。