十大經典排序算法江山圖十大排序分類java
終於來到了最後兩個算法,非比較類的線性時間複雜度算法,計數排序和基數排序。上一篇也提到過,這幾種排序算法理解起來都不難,時間、空間複雜度分析起來也很簡單,可是對要排序的數據要求很苛刻,上一篇提到的桶排序就是適用於外部排序中,即所謂的外部排序就是數據存儲在外部磁盤中,數據量比較大,內存有限,沒法將數據所有加載到內存中。算法
計數排序實際上是桶排序的一種特殊狀況。當要排序的n個數據,所處的範圍並不大的時候,好比最大值是k,咱們就能夠把數據劃分紅k個桶。每一個桶內的數據值都是相同的,省掉了桶內排序的時間。數組
例如高考查分系統,系統會顯示咱們的成績以及所在省的排名。若是你所在的省有50萬考生,如何經過成績快速排序得出名次呢?微信
❝考生的滿分是900分,最小是0分,這個數據的範圍很小,因此咱們能夠分紅901個桶,對應分數從0分到900分。根據考生的成績,咱們將這50萬考生劃分到 這901個桶裏。桶內的數據都是分數相同的考生,因此並不須要再進行排序。咱們只須要依次掃描每一個桶,將桶內的考生依次輸出到一個數組中,就實現了50萬考生的排序。由於只涉及掃描遍歷操做,因此時間複雜度是O(n)。ide
❞
計數排序的算法思想就是這麼簡單跟桶排序很是相似,只是桶的大小粒度不同
。url
假設咱們有10萬個手機號碼,但願將這10萬個手機號碼從小到大排序,用什麼方法快速排序?spa
❝對於桶排序、計數排序,手機號碼有11位,範圍太大,顯然不適合用這兩 種排序算法。手機號碼有這樣的規律:假設要比較兩個手機號碼a,b的大小,若是在前面幾位中,a手機號碼已經比b手機號碼大了,那後面的幾位就不用看了。根據這個思路,先按照最後一位來排序手機號碼,而後,再按照倒數第二位從新排序,以此類推,最後按照第一位從新排序。通過11次排序以後,手機號碼就都有序了。code
❞
這就是基數排序的算法思路,基數排序對要排序的數據是有要求的,須要能夠分割出獨立的「位」來比較,並且位之間有遞進的關係,若是「a」數據的高位比「b」數據大,那剩下的低 位就不用比較了。除此以外,每一位的數據範圍不能太大,要能夠用線性排序算法來排序,不然,基數排序的時間複雜度就沒法作到 O(n) 了。orm
就是把數組元素值做爲數組的下標,而後用一個臨時數組統計該元素出現的次數,例如 temp[i] = m, 表示元素 i 一共出現了 m 次。最後再把臨時數組統計的數據從小到大彙總起來,此時彙總起來是數據是有序的。blog
計數排序
由圖可知,計數排序須要開闢一個臨時數組來存儲,先遍歷原數組一個個放入,而後再遍歷臨時數組一個個取出。
public class CountSort {
public static int[] countSort(int[] arr) {
if (arr == null || arr.length < 2) {
return arr;
}
int length = arr.length;
int max = arr[0];
int min = arr[0];
// 數組的最大值與最小值
for (int i = 1; i < arr.length; i++) {
if(arr[i] < min) {
min = arr[i];
}
if(max < arr[i]) {
max = arr[i];
}
}
// 建立大小爲max的臨時數組
int[] temp = new int[max + 1];
// 統計元素i出現的次數
for (int i = 0; i < length; i++) {
temp[arr[i]]++;
}
// 把臨時數組統計好的數據彙總到原數組
int k = 0;
for (int i = 0; i <= max; i++) {
for (int j = temp[i]; j > 0; j--) {
arr[k++] = i;
}
}
return arr;
}
public static void main(String[] args) {
int[] arr = {21,4,12,42,46,23,27,11,6,5,33,29,41,46,40,13,31};
arr = countSort(arr);
System.out.print("數組排序以後:");
for (int i=0; i<arr.length; i++) {
System.out.print(arr[i] + ",");
}
}
}
O(n+k)
O(n+k)
穩定
計數排序只能用在數據範圍不大的場景中,若是數據範圍「k」比要排序的數據「n」大不少,就不適合用計數排序了。並且,計數排序只能給非負整數排序,若是要排序的數據是其餘類型的,要將其在不改變相對大小的狀況下,轉化爲非負整數。
好比,仍是拿考生這個例子。若是考生成績精確到小數後一位,咱們就須要將全部的分數都先乘以10,轉化成整數,而後再放到9010個桶內。再好比,若是要排序的數據中有負數,數據的範圍是[-1000, 1000],那咱們就須要先對每一個數據都加1000,轉化成非負整數,由於數組的下邊不多是負數。
分別以個,十,百...位上數字大小對數組進行排序,最後概括彙總獲得總體有序的數組。
基數排序
這裏的數最多兩位數,少於兩位數的比較十位數的時候,能夠十位數補0比較:
第一遍是按照個位數排的,獲得的數組是個位數有序;
第二遍再按照十位數排,獲得的數組所有有序;
import java.util.ArrayList;
public class RadixSort {
public static int[] radixSort(int[] arr) {
if(arr == null || arr.length < 2) return arr;
int n = arr.length;
int max = arr[0];
// 最大值
for (int i = 1; i < n; i++) {
if (max < arr[i]) {
max = arr[i];
}
}
// 計算最大值是幾位數
int num = 1;
while (max / 10 > 0) {
num++;
max = max / 10;
}
// 建立10個桶
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>(10);
//初始化桶
for (int i = 0; i < 10; i++) {
bucketList.add(new ArrayList<Integer>());
}
// 進行每一趟的排序,從個位數開始排
for (int i = 1; i <= num; i++) {
for (int j = 0; j < n; j++) {
// 獲取每一個數最後第 i 位是數組
int radio = (arr[j] / (int)Math.pow(10,i-1)) % 10;
//放進對應的桶裏
bucketList.get(radio).add(arr[j]);
}
//合併放回原數組
int k = 0;
for (int j = 0; j < 10; j++) {
for (Integer t : bucketList.get(j)) {
arr[k++] = t;
}
//取出來合併了以後把桶清光數據
bucketList.get(j).clear();
}
}
return arr;
}
public static void main(String[] args) {
int[] arr = {21,4,12,42,46,23,27,11,6,5,33,29,41,46,40,13,31};
arr = radixSort(arr);
System.out.print("數組排序以後:");
for (int i=0; i<arr.length; i++) {
System.out.print(arr[i] + ",");
}
}
}
O(n*k),k表明桶的個數
O(n+k),k表明桶的個數
穩定。
須要能夠分割出獨立的「位」來比較,並且位之間有遞進的關係,若是「a」數據的高位比「b」數據大,那剩下的低位就不用比較了。除此以外,每一位的數據範圍不能太大,要能夠用線性排序算法來排序,不然,基數排序的時間複雜度就沒法作到O(n)了。
歡迎點贊分享在看,關注公衆號《阿甘的碼路》看更多內容,有問題能夠加我微信私我指正,各大平臺也會同步只不過排版可能有些會不太同樣~