三種排序演算法
一、氣泡排序
package com.light.springboot.algorithm; import com.alibaba.fastjson.JSON; public class SortMaopao { public static void main(String[] args) { int[] intArray = {0,1,2,3,4}; System.out.println(JSON.toJSON(maopaoSort(intArray))); } /** * 氣泡排序法 * 氣泡排序是由兩個for迴圈構成,第一個for迴圈的變數 i 表示總共需要多少輪比較,第二個for迴圈的變數 j 表示每輪參與比較的元素下標【0,1,......,length-i】 * 因為每輪比較都會出現一個最大值放在最右邊,所以每輪比較後的元素個數都會少一個,這也是為什麼 j 的範圍是逐漸減小的。相信大家理解之後快速寫出一個氣泡排序並不難。 * @param array * @return */ public static int[] maopaoSort(int[] array){ int count = 0;//排序輪數 boolean flag = true;//設定一個標記,若為true,則表示此次迴圈沒有進行交換,也就是待排序列已經有序,排序已經完成。 //i為一共要比較多少輪 for (int i = 1; i < array.length; i++) { //j為參與比較的陣列下標範圍,每比較一輪,最大值放在最右邊,下一輪參與比較的元素就減一 for (int j = 0; j < array.length-i; j++) { //如果前面的數比後面的大,就交換兩個資料的位置 if (array[j] > array[j+1]) { int tmp = array[j]; array[j] = array[j+1]; array[j+1]=tmp; flag = false;//有交換,說明比較沒完成 } } count++;//計數 //flag為true,說明本輪比較無交換,排序已完成 if (flag) { break; } } System.out.println("排序" + count + "輪"); return array; } }
氣泡排序解釋:
氣泡排序是由兩個for迴圈構成,第一個for迴圈的變數 i 表示總共需要多少輪比較,第二個for迴圈的變數 j 表示每輪參與比較的元素下標【0,1,......,length-i】,因為每輪比較都會出現一個最大值放在最右邊,所以每輪比較後的元素個數都會少一個,這也是為什麼 j 的範圍是逐漸減小的。相信大家理解之後快速寫出一個氣泡排序並不難。
氣泡排序效能分析:
假設參與比較的陣列元素個數為 N,則第一輪排序有 N-1 次比較,第二輪有 N-2 次,如此類推,這種序列的求和公式為:
(N-1)+(N-2)+...+1 = N*(N-1)/2
當 N 的值很大時,演算法比較次數約為 N2
假設資料是隨機的,那麼每次比較可能要交換位置,可能不會交換,假設概率為50%,那麼交換次數為N2/4。不過如果是最壞的情況,初始資料是逆序的,那麼每次比較都要交換位置。
交換和比較次數都和N2成正比。由於常數不算大 O 表示法中,忽略 2 和 4,那麼氣泡排序執行都需要 O(N2) 時間級別。
其實無論何時,只要看見一個迴圈巢狀在另一個迴圈中,我們都可以懷疑這個演算法的執行時間為 O(N2)級,外層迴圈執行 N 次,內層迴圈對每一次外層迴圈都執行N次(或者幾分之N次)。這就意味著大約需要執行N2次某個基本操作。
二、選擇排序
package com.light.springboot.algorithm; /** * 選擇排序法 * 一共排i輪,第 i 輪選出 i 之後最小的值,放在第i個位置 * 選擇排序和氣泡排序執行了相同次數的比較:N*(N-1)/2,但是至多隻進行了N次交換。 * 當 N 值很大時,比較次數是主要的,所以和氣泡排序一樣,用大O表示是O(N2) 時間級別。但是由於選擇排序交換的次數少,所以選擇排序無疑是比氣泡排序快的。 * 當 N 值較小時,如果交換時間比選擇時間大的多,那麼選擇排序是相當快的。 * @author qiaozhong * */ public class Sortxuanze { public static void main(String[] args) { int[] intArray = {0,5,3,7,8,8,9,6,5,4}; for (int i = 0; i < intArray.length; i++) { System.out.println(xuanzeSort(intArray)[i]); } } public static int[] xuanzeSort(int[] intArray){ for(int i=0; i < intArray.length; i++){ for(int j=i; j<intArray.length; j++){ if (intArray[i] > intArray[j]) { int tmp = intArray[j]; intArray[j] = intArray[i]; intArray[i] = tmp; } } } return intArray; } }
選擇排序效能分析:
選擇排序和氣泡排序執行了相同次數的比較:N*(N-1)/2,但是至多隻進行了N次交換。
當 N 值很大時,比較次數是主要的,所以和氣泡排序一樣,用大O表示是O(N2) 時間級別。但是由於選擇排序交換的次數少,所以選擇排序無疑是比氣泡排序快的。當 N 值較小時,如果交換時間比選擇時間大的多,那麼選擇排序是相當快的。
三、插入排序
package com.light.springboot.algorithm; /** * 插入排序法 * 直接插入排序基本思想是每一步將一個待排序的記錄,插入到前面已經排好序的有序序列中去,直到插完所有元素為止。 * @author qiaozhong * */ public class SortCharu { public static void main(String[] args) { int[] intArray = {0,5,3,7,8,8,9,6,5,4}; for (int i = 0; i < intArray.length; i++) { System.out.println(charuSort(intArray)[i]); } } /** * @param intArray * @return */ public static int[] charuSort(int[] intArray){ //預設首個元素自己為有序的,所以從第二個元素開始拍訊,迴圈length-1次 for (int i=1; i < intArray.length; i++) { //記錄要插入的資料 int j = i; int tmp = intArray[j]; //若當前值小於前面元素值,就把前面元素向後移動一個位置,直到當前值大於前面元素數值,或者當前值移動到陣列首位 while(j>0 && tmp < intArray[j-1]){ intArray[j] = intArray[j-1]; j--; } intArray[j] = tmp;//要插入的資料存插入到正確位置 } return intArray; } }
插入排序效能分析:
在第一輪排序中,它最多比較一次,第二輪最多比較兩次,一次類推,第N輪,最多比較N-1次。因此有 1+2+3+...+N-1 =N*(N-1)/2。
假設在每一輪排序發現插入點時,平均只有全體資料項的一半真的進行了比較,我們除以2得到:N*(N-1)/4。用大O表示法大致需要需要 O(N2) 時間級別。
複製的次數大致等於比較的次數,但是一次複製與一次交換的時間耗時不同,所以相對於隨機資料,插入排序比冒泡快一倍,比選擇排序略快。
這裡需要注意的是,如果要進行逆序排列,那麼每次比較和移動都會進行,這時候並不會比氣泡排序快。
總結:
上面講的三種排序,冒泡、選擇、插入用大 O 表示法都需要O(N2) 時間級別。一般不會選擇氣泡排序,雖然氣泡排序書寫是最簡單的,但是平均效能是沒有選擇排序和插入排序好的。
選擇排序把交換次數降低到最低,但是比較次數還是挺大的。當資料量小,並且交換資料相對於比較資料更加耗時的情況下,可以應用選擇排序。
在大多數情況下,假設資料量比較小或基本有序時,插入排序是三種演算法中最好的選擇。
後面我們會講解高階排序,大O表示法的時間級別將比O(N2)小。