冒泡,插入,選擇這三種基礎的排序算法,比較簡單效率不高,工做中通常不會使用,可是當作算法來研究仍是能瞭解一些知識的,本文以<數據結構與算法之美>爲基礎,詳細解析一下.java
首先要引入幾個概念git
若是待排序數組中有相同的元素,排序事後它們的相對順序不發生變化. 好比 2,9,3,4,8,3
排序事後爲2, 3 , 3, 4, 8, 9
這兩個3的相對順序不變.這樣就是具備穩定性. 穩定性能夠保證複雜數據結構排序時的相對有序性. 好比咱們要對一筆訂單先按金額排列,金額相同的再按時間排序
就能夠先將這筆訂單按照時間排序,再採用具備穩定性的排序算法按金額進行排序. 猜想Java8中的compare().thenCompare()就是採用這種原理實現github
排序過程當中只佔用常數級的額外空間或者不佔用額外空間算法
數組中具備有序關係的元素對的個數,數學表達式爲 有序元素對:a[i] <= a[j], 若是i < j。
2,4,3,1,5,6
的有序度爲11,有序元素對爲(2,4),(2,3),(2,5),(2,6),(4,5),(4,6),(3,5),(3,6),(1,5),(1,6),(5,6)
對 6,5,4,3,2,1
這個徹底逆序的數組,有序度爲0
對 1,2,3,4,5,6
這個徹底有序的數組,有序度爲(n-1)+(n-2)+(n-3)+...+0 = n*(n-1)/2 = 15
,即滿有序度數組
和有序度定義正好相反 逆序度 = 滿有序度 - 有序度數據結構
爲了方便描述,這裏約定數組爲arr[0~n],n≥1,排列方式爲正序(從小到大).
下面的描述不會舉例講解,推薦到 visualgo查看動畫演示性能
首先將數組在邏輯劃分爲左側的未排序
部分和右側的已排序
部分
冒泡排序中對未排序
部分的一次遍歷稱爲一次冒泡,初始時未排序部分爲arr[0~n-1]
,未排序部分爲空.冒泡一次後,未排序部分爲arr[0~n-2]
,已排序部分爲arr[n-1~n-1
],冒泡x
(1≤x<n
)次後,未排序部分爲arr[0~n-1-x)
,已排序部分爲arr[n-x~n-1]
,最終達成所有排序
每次冒泡未排序
部分時,只會操做相鄰的元素,方向爲 arr[0]→ arr[n-1],若是a[i]>a[i+1]
就進行交換,這樣一次冒泡操做後,未排序
部分中的最大值就會移動到已排序
部分且在最左側(即已排序部分的最小值).
同時,若是一次冒泡後沒有進行過任何交換說明整個數組已經有序了,結合這個特性能夠省去沒必要要的冒泡,核心代碼以下學習
public void sort(Comparable[] a) { if (a.length <= 1) { return; } for (int i = a.length - 1; i > 0; i--) { boolean changeFlag = false; for (int j = 0; j < i; j++) { if (a[j + 1].compareTo(a[j]) < 0) { swap(a, j + 1, j); changeFlag = true; } } if (!changeFlag) { break; } } }
最壞狀況下,數組徹底倒序, 時間複雜度爲 (n-1)+(n-2)+(n-3)+...+1 = (n^2)/2 ~= O(n^2) 數字表示交換的次數
最好狀況下,數組已經有序,只須要一次冒泡, 時間複雜度爲 O(n) 這裏不會有交換,以比較的次數做爲計量單位
平均狀況下,用機率推算會很困難,這裏採用上面提到的有序度
和逆序度
,並將元素的交換次數當作計量單位, 觀察能夠看到,每交換一次,都會使原來逆序的一組元素變爲有序,逆序度即爲交換次數,那麼在最好狀況下逆序度爲0,最壞狀況下逆序度爲n(n-1)/2,平均時間複雜度爲二者取平均值
(0 + n(n-1)/2)/2 = O(n^2)測試
冒泡排序涉及到兩個元素的交換,交換時只使用了一個單位臨時空間,是原地排序動畫
冒泡排序中元素位置發生變化交換時,而且交換僅在a[i]>a[i+1]時才發生,a[i]==a[i+1]時時不會發生的,所以是穩定的
將數組分爲左側
的已排序部分和右側
的未排序部分
初始時已排序部分爲arr[0~0]
,未排序部分爲arr[1~n-1]
一次插入後,已排序部分爲arr[0~1]
,未排序部分爲arr[2~n-1]
,x(1≤x<n)
次插入後,已排序部分爲arr[0x],未排序部分爲arr(x+1n-1]
從未排序部分的起始位置開始(即arr[1]),每次插入時,方向爲 arr[n-1]→ arr[0],找出第一個小於等於arr[i]
的元素,有兩種狀況
public void sort(Comparable[] arr) { if (arr.length <= 1) { return; } for (int i = 1; i < arr.length; i++) { Comparable insertValue = arr[i]; int insertPos = i; for (int j = i - 1; j >= 0; j--) { if (insertValue.compareTo(arr[j]) < 0) { arr[j + 1] = arr[j]; insertPos = j; } else { insertPos = j + 1; break; } } arr[insertPos] = insertValue; } }
以元素移動的次數做爲計量單位
最壞狀況下,數組徹底倒序,時間複雜度爲 0 + 1 + 2 + 3 +...+ (n-1) = n*(n-1)/2 ~= O(n^2)
最好狀況下,數組徹底順序, 時間複雜度爲 1 + 1 + 1 + ... + 1= n ~=O(n)
平均狀況下, 咱們仍然使用逆序數這個概念,再觀察依舊能夠得知: 逆序數等於移動次數,因此時間複雜度爲 (0 + n(n-1)/2)/2 ~= O(n^2)
插入排序中,沒有使用額外的空間,所以是原地排序
插入排序中,只有移動操做會改變元素位置,而斷定條件是小於等於arr[i]
,符合狀況1,a[i]會被設置到a[j+1],從而保證相對順序的穩定.
將數組分爲左側的已排序
部分和右側的未排序
部分
選擇排序中選擇表示對未排序部分的一次遍歷,初始時已排序部分爲空,未排序部分爲arr[0~n-1]
,
一次選擇後,已排序部分爲arr[0~0]
,未排序部分爲arr[1~n-1]
,x(1≤x<n)
次選擇後,已排序部分爲arr[0~x-1]
,未排序部分爲arr(x~n-1]
第x(1≤x<n)
次選擇時,找出未排序數組中的最小值,放入已排序數組(與arr[x-1]
交換),核心代碼以下
public void sort(Comparable[] arr) { if (arr.length <= 1) { return; } for (int i = 1; i < arr.length ; i++){ int minPos=i-1; Comparable minValue=arr[i-1]; for(int j=i;j<arr.length;j++){ if (minValue.compareTo(arr[j])>0){ minPos=j; minValue=arr[j]; } } swap(arr,minPos,i-1); } }
以minValue被替換的次數做爲計量單位.
最壞狀況下爲 (n-1) + (n-2) + ... + 0 =n(n-1)/2 ~= O(n^2)
最好狀況下爲 1+1+1...+1 =n ~= O(n)
平均狀況下,仍然使用逆序數, 能夠發現: 逆序數等於替換的次數,所以時間複雜度爲(0 + n(n-1)/2)/2 ~= O(n^2)
選擇排序中,minValue使用了一個單位的額外空間,所以是原地排序
選擇排序中,沒有約束能夠確保穩定, 好比 4, 3, 2, 4, 1
排序事後,兩個4
的相對位置會發生變化
三者的相同之處
已排序和未排序
部分,在排序過程當中逐步將未排序部分轉化爲已排序部分.三者的不一樣之處
不穩定
的選擇>插入>冒泡
, 可是選擇排序不穩定,僅此一點不少場景下就不適用,實用性大大下降. 所以插入排序時三者中較好的選擇.冒泡,插入,選擇是三種基本的排序算法,研究他們對學習更高效的排序算法有幫助,相關源碼已經上傳到這裏