如何排序?java
這一章中主要是三個比較簡單的算法:冒泡排序、選擇排序和插入排序。計算機程序不能像人同樣通覽全部的數據。它只能根據計算機的「比較」操做原理,在同一時間內對來個數據項進行比較。
算法
這三種算法都包括以下兩個步驟,這兩部循環執行,直到所有數據有序爲止;
數組
一、比較兩個數據項;
編碼
二、交換兩個數據項,或複製其中一項。
spa
可是,每種算法具體實現的細節有所不一樣。
調試
冒泡排序code
冒泡排序算法運行起來很是慢,但在概念上它是排序算法中最簡單的,所以冒泡排序算法在剛開始研究排序技術時時一個很是好的算法。
對象
冒泡排序的Java代碼:排序
public class ArrayTest { public static void main(String[] args) { //冒牌排序法 int[] a = {34,21,5,2,3,12,56,13,37,22}; for (int i = 0; i < a.length; i++) { System.out.print(a[i]+","); } System.out.println(""); for (int i = a.length-1; i >1; i--) { for (int j = 0; j < i; j++) { if(a[j]>a[j+1]){ int temp=a[j]; a[j] = a[j+1]; a[j+1] = temp; } } } for (int i = 0; i < a.length; i++) { System.out.print(a[i]+","); } } } //輸出: 34,21,5,2,3,12,56,13,37,22, 2,3,5,12,13,21,22,34,37,56,
這個算法的思路是要將最小的數據項放在數組的最開始(數組的下標爲0),並將最大的數據項放在數組的最後(數組下標爲nElems-1)。外層for循環的計數器 i 從數組的最後開始,即i等於i等於nEmels-1,沒通過一次循環i減一。小標大於i的數據項都已經排好序的了。變量i在每完成一次內部循環(計數器爲j)後左移一位,所以算法就再也不處理那些已經排好序的數據了。
內存
內層for循環計數器j從數組的最開始算起,即j=0,沒完成一次內部循環體加一,當它等於out時結束一次循環。在內層for循環體中,數組小標爲j和j+1的兩個數據項進行比較,若是小標爲j的數據項大於小標爲j+1的數據項項,則交換兩個數據項。
不變性
在許多算法中,有些條件在算法執行時時不變的。這些條件被稱爲不變性。這些條件被稱爲不變性。認識不變性對理解算法是有用的。在必定狀況先他們對調試也有用;能夠反覆檢查不變性爲否爲真,若是不是的話就標記出錯。
在上述代碼中不變性是指i右邊的數據項爲有序。在算法的整個運行過程當中這個條件始終爲真。(在第一次排序開始前,還沒有排序,由於i開始時在數據項的最左邊,沒有數據項在i的右邊。)
冒泡排序法的效率
經過分析10分數據項的數組,第一趟排序時進行了9次比較,第二趟排序進行了8次比較。如此類推,知道最後一趟進行了一次比較。對於10個數據項就是
9+8+7+6+5+4+3+2+1=45
通常來講數組中有N個數據項,則第一趟排序中有N-1次比較,第二趟中有N-2次比較,如此類推公式以下
(N-1)+(N-2)+(N-3)+…+1=N(N-1)/2
當N爲10時,N*(N-1)/2等於45 (10*9/2)。
這樣,算法做了約N²/2次比較(忽略減一,不會有很大的差異,特別是當N很大時)。
選擇排序
選擇排序改進了冒泡排序,將必要的交換次數從O(N²)減小到O(N)。不幸的是比較次數仍保持爲O(N²)。然而,選擇排序仍然爲大記錄量的排序提出了一個很是重要的改進,由於這些大量的記錄須要在內存中移動,這就使交換的時間和比較的時間相比起來,交換的時間更爲重要。(在Java中只是改變了引用位置,而實際對象的位置並無發生改變。)
選擇排序的Java代碼
public static void main(String[] args) { //選擇排序法 int[] a = {34,21,5,2,3,12,56,13,37,22}; for (int i = 0; i < a.length; i++) { System.out.print(a[i]+","); } System.out.println(""); int out,in,min; for (out = 0; out < a.length-1; out++) { min = out; for (in = out+1; in < a.length; in++) { if(a[in]<a[min]){ min=in; } int temp = a[out]; a[out] = a[min]; a[min] = temp; } } for (int i = 0; i < a.length; i++) { System.out.print(a[i]+","); } } //輸出: //34,21,5,2,3,12,56,13,37,22, //2,5,12,3,13,21,22,37,34,56,
外層循環用循環變量out,從數組開頭開始(數組下標爲0)向高位增加。內層循環變量in,從out指位置開始,一樣是向右移位。
在每個in的新位置,數據項a[in]和a[min]進行比較,若是a[in]更小,則min被賦值爲in的值,在內層循環的最後,min指向最小的數據項,而後out和min指向的數組數據項。
不變性
在上述代碼程序中,下標小於或等於out的位置的數據項老是有序的。
選擇排序的效率
選擇排序和冒泡排序執行了相同次數的比較:N*(N-1)/2。對於10個數據項,須要4次比較。然而10個數據項只須要少於10次交換。對於100個數據項,須要4950次比較,但只進行了不到100次的交換。N值很大時,比較的次數是主要的,因此結論是選擇排序和冒泡排序同樣運行了O(N²)時間。可是,選擇排序無疑更快,由於它進行交換少得多。當N值較小時,特別是若是交換的時間比比較的時間級大得多時,選擇排序其實是至關快的。
插入排序
插入排序的Java代碼
public static void main(String[] args) { //插入排序 int[] a = {34,21,5,2,3,12,56,13,37,22}; for (int i = 0; i < a.length; i++) { System.out.print(a[i]+","); } System.out.println(""); for (int i = 1; i < a.length; i++) { int temp = a[i]; int j = i; while(j>0&&a[j-1]>=temp){ a[j] = a[j-1]; --j; } a[j] = temp; } for (int i = 0; i < a.length; i++) { System.out.print(a[i]+","); } } //輸出: //34,21,5,2,3,12,56,13,37,22, //2,5,12,3,13,21,22,37,34,56,
在外層的for循環總,i變量從1開始,向右移動。標記了未排序部分的最左端的數據。而內層while循環中,j變量從i變量開始向左移動,直到temp變量小於j所指的數組數據項,或者它已經不能再往左移動爲止。while循環的每一趟都向右移動了一個已排序的數據項。
插入排序中的不變性:
在每趟結束時,在將temp位置的項插入後,比i變量下標號小的數據項都是局部有序的。
插入排序的效率
這個算法須要多少次比較和複製呢?在第一趟排序中,他最多比較一次,第二趟最多比較兩次,以此類推。最後一趟最多,比較N-1次。一次有
1+2+3+... +N-1=N*(N-1)/2
然而,由於在每一趟排序發現插入點以前,平均只有全體數據項的一半真的進行了比價,咱們除以2獲得
N*(N-1)/ 4
複製的次數大體等於比較的次數。然而,一次複製與一次交換的時間耗費不一樣,因此相對於隨機數據,這個算法比冒泡排序快一倍,比選擇排序略快。
對於已經有序或基本有序的數據來講,插入排序要好得多。當數據有序的時候,while循環的條件老是false因此它變成了外層循環中一個簡單語句,執行N-1次。在這種狀況下,算法運行只須要O(N)的時間。若是數據基本有序,插入排序幾乎只須要O(N)的時間,這對把一個基本有序進行排序是一個簡單而有效的方法。
然而對於逆序排列的數據,每次比較和移動都會執行,因此插入排序不比冒泡排序快。
對象排序
上述都是簡單的數值排序,排序例程卻更多地應用於對象排序,而不是對基本數據類型的排序。
對象排序的Java代碼
public void insertSort(){ for (int i = 1; i < a.length; i++) { Person temp = a[i]; int j = i; while(j>0 && a[j-1].getLast().compareTo(temp.getLast())>0){ a[j]=a[j-1]; --j; } a[j] = temp; } }
單詞排序
compareTo()方法更具兩個String的字典順序(字母序)返回給調用者不一樣的整數值,這兩String一個是方法的調用者,一個是這個方法的參數。
穩定性
有些時候,排序要考慮數據項擁有相同關鍵字的狀況。例如,僱員數據按僱員的姓的字典排序(排序以姓爲關鍵字),如今又想按郵政編碼排序,並但願具備相同郵政編碼的數據仍然按姓排序。這宗狀況下,則值須要算法對須要排序的數據進行排序,讓不須要排序的數據保持原來的順序。某些算法知足這樣的要求,它們就能夠成爲穩定的算法。
幾種簡單排序之間的比較
除非手邊沒有算法書能夠參考,通常狀況下幾乎不太實用冒泡排序算法。它太過於簡單,以致於能夠絕不費力地寫出來。然而當數據量很小的時候它會有些應用價值。
選擇排序當數據量很小,而且交換數據相對於比較數據更加消耗的狀況下,能夠應用選擇排序。
當數據量比較小或基本上有序時,插入排序是這三種最好的選擇。
小 結
本章提到的排序算法都假定了數組做爲數據存儲機構。
排序包括比較數組中數據項的關鍵字和移動相應數據項直到它們排好序爲止。
本章全部算法的時間複雜度都是O(n²)。不過,某些狀況下某個算法能夠比其餘算法快不少。
不變性是指在算法運行時保持不變的條件。
冒泡排序算法是效率最差的算法,但他很簡單。
插入算法是本章上述介紹的O(n²)排序算法中應用最多的。
若是具備相同關鍵字的數據項,通過排序它們的順序保持不變,這樣的排序就是穩定的。
上述介紹的全部排序算法除了須要初始數組以外,都只須要一個臨時變量