身在斯洛文尼亞的阿拉里克獲得斯提里科被殺的消息後,仰天大笑:「終於沒有人能阻止我去羅馬了。」
當他手下的將軍問:「不知大王打算走哪條路去羅馬?」
西哥特王哈哈大笑,說出了那句千古名言: All roads lead to Rome
條條大路通羅馬,這句著名的英語諺語告訴人們,達到同一目的能夠有多種不一樣的方法和途徑。
在編程中一樣如此,一樣一個編程問題,十個程序員可能會寫出十種程序,算法各不相同,確實是條條大路通羅馬。
但程序員對算法的效率可不會像西哥特王那般豪放和豁達,程序員對於算法的效率十分在意,儘管算法不管效率高低,均能解決問題,但效率高的算法,除了能解決問題,還能夠在問題規模變大變複雜時,高效地解決問題。java
因而,咱們面臨一個問題,那就是如何評價,以及從什麼角度去評價一個算法。程序員
一般,咱們可能說,這個算法比那個算法更快一點,但這個比較是沒有意義的。當算法要處理的數據項數量不一樣時,誰快誰慢都要從新評價。算法
評價算法時,應該結合數據量來評價,即當數據量增大時,算法所消耗的時間變化趨勢。編程
在計算機世界中,這種粗略的評價方式被稱爲大O表示法數組
冒泡排序先從數組最左邊開始,比較第1個和第2個元素的值,值比較高的往數組的高位排,而後依次比較第2和第3個元素,值比較大的往高位排,一直比較到倒數第2個和倒數第1個元素,這稱爲第一趟排序,這一趟就能肯定數組中值最大的那個元素,並把這個最大的元素排到數組的最高位。
依次類推,第二趟排序會肯定數組中第二大的元素,並把它排在最大的元素前邊。
假設數組有n個元素,那麼通過n-1趟排序,數組的元素就是有序的。
由於每一趟中,最大的元素就像水泡同樣,冒到了數組的高位,冒泡排序所以得名。微信
/** * * * @author beanlam * @date 2016年3月9日 下午11:26:20 * @version 1.0 * */ public class SimpleSort { public static void bubbleSort(final int[] array) { if (array == null || array.length == 0) { throw new IllegalArgumentException("array"); } int length = array.length; for (int outLoop = length - 1; outLoop > 0; outLoop-- ) { for (int innerLoop = 0; innerLoop < outLoop; innerLoop++) { if (array[innerLoop] > array[innerLoop + 1]) { swap(array, innerLoop, innerLoop + 1); } } } } private static void swap(final int[] array, final int left, final int right) { int temp = array[left]; array[left] = array[right]; array[right] = temp; } }
選擇排序的過程是從左向右掃描數組,並從中找出最小值的元素,把它放在左邊已知的最小位置上,好比第一趟掃描,找出最小的元素後,將該元素放到數組的下標0處。第二趟掃描從下標1開始掃描,找出最小元素後,放到下標1處。總共須要掃描n-1次,就能使該數組處於有序狀態。oop
/** * * * @author beanlam * @date 2016年3月9日 下午11:26:20 * @version 1.0 * */ public class SimpleSort { public static void selectionSort(final int[] array) { if (array == null || array.length == 0) { throw new IllegalArgumentException("array"); } int length = array.length; int minIndex; for (int outLoop = 0; outLoop < length - 1; outLoop++) { minIndex = outLoop; for (int innerLoop = outLoop + 1;innerLoop < length; innerLoop++) { if (array[innerLoop] < array[minIndex]) { minIndex = innerLoop; } } swap(array, outLoop, minIndex); } } private static void swap(final int[] array, final int left, final int right) { int temp = array[left]; array[left] = array[right]; array[right] = temp; } }
插入排序的精髓在於先令局部有序,先令左邊一部分數據有序,而後這部分有序的元素的下一位再與這些有序的元素比較,尋找合適本身站立的位置,插隊排進去,插隊也意味着右邊的有序元素要挪動身子。
一下提供基於for循環和while循環的兩種插入排序實現方式:spa
/** * * * @author beanlam * @date 2016年3月9日 下午11:26:20 * @version 1.0 * */ public class SimpleSort { public static void insertionSort(final int[] array) { if (array == null || array.length == 0) { throw new IllegalArgumentException("array"); } int length = array.length; for (int outLoop = 1; outLoop < length; outLoop++) { int temp = array[outLoop]; for (int innerLoop = outLoop - 1; innerLoop >= 0; innerLoop--) { if (array[innerLoop] > temp) { array[innerLoop + 1] = array[innerLoop]; if (innerLoop == 0) { array[innerLoop] = temp; } } else { array[innerLoop + 1] = temp; break; } } } } public static void insertionSort1(final int[] array) { if (array == null || array.length == 0) { throw new IllegalArgumentException("array"); } int length = array.length; int temp; int innerLoop; for (int outLoop = 1; outLoop < length; outLoop++) { temp = array[outLoop]; innerLoop = outLoop; while (innerLoop > 0 && array[innerLoop - 1] >= temp) { array[innerLoop] = array[innerLoop-1]; --innerLoop; } array[innerLoop] = temp; } } private static void swap(final int[] array, final int left, final int right) { int temp = array[left]; array[left] = array[right]; array[right] = temp; } }
假設須要比較的數組中有N個元素,
冒泡排序中,須要掃描N-1趟,每掃描一趟就要屢次對兩個元素作比較,而且在必要時須要對兩個元素作位置的交換,因爲數據是隨機的,因此平均下來,一趟中大概有一半被掃描的數據須要做位置的交換。
第1趟須要N-1次比較,第2趟須要N-2次比較,以此類推,總共須要N(N-1)/2趟比較,而元素的交換次數平均下來須要作NN/4次。code
選擇排序和冒泡排序進行了相同次數的比較N*(N-1)/2,但每一趟只有一次交換,甚至沒有任何交換。所以,選擇排序比冒泡排序更有效率,由於它減小了不少交換。排序
插入排序卻又要比選擇排序更有效率一點,由於第1趟排序中,它最多比較1次,第2趟排序中,最多比較2次, 依次類推,最後一趟,最多比較N-1次,
平均只有全體數據的一半被比較,所以比較的次數爲N*(N-1)/4,與冒泡和選擇排序不一樣的是,插入排序不須要交換數據,只是把一個值賦給數組的某一個下標,賦值的速度比交換數據的速度要快不少,所以插入排序比選擇排序和冒泡排序更有效率。
大O表示法只是一個粗略的估算值,它關注與隨着數據量N的增大,算法速度的變化。
對於數組某個下標的賦值,算法消耗的時間是個常數K
對於線性的查找,算法的消耗時間與N成正比。
對於二分查找,算法消耗時間與log(N)成正比。
大O表示法一般會忽略常數,由於它關注的是算法的消耗時間隨着N的變化而怎麼變化。常數一般與處理器芯片或者編譯器有關。
在上面的三種排序中,它們的效率爲用大O表示法來表示都是O(N^2),但實際上按比較的次數和交換的次數來考慮,插入排序效率高於選擇排序,選擇排序效率高於冒泡排序。
大O表示法常見的基於N的走勢圖以下圖所示: