排序算法概念
排序也稱排序算法?(Sort Algorithm),排序是將一?組數據,依指定的順序進行排列?的過程。
排序的分類:
- 內部排序:
指將需要處理的所有數據都加載?到內部存儲器中進行排序。 - 外部排序法:
數據量過大,無法全部加載到內?存中,需要借助外部存儲進行
排序。 - 常見的排序算法分類(見右圖):
時間復雜度
要知道各個算法的執行性能和優劣,需要理解時間復雜度是如何計算和概念以及根據時間復雜度選取適合的算法進行排序。可以看下面這篇文章:
算法優劣指標之時間復雜度如何計算總結:
https://juejin.im/post/5d81fcf6e51d4561c94b107a
八大算法的時間復雜度總結
冒泡排序
基本介紹
冒泡排序(Bubble Sorting)的基本思想是:通過對待排序序列從前向后(從下標較小的元素開始),依次比較相鄰元素的值,若發現逆序則交換,使值較大的元素逐漸從前移向后部,就象水底下的氣泡一樣逐漸向上冒。
優化
因為排序的過程中,各元素不斷接近自己的位置,如果一趟比較下來沒有進行過交換,就說明序列有序,因此要在排序過程中設置一個標志flag判斷元素是否進行過交換。從而減少不必要的比較。(這里說的優化,可以在冒泡排序寫好后,在進行)。
小結冒泡排序規則
(1) 一共進行 數組的大小-1 次 大的循環
(2)每一趟排序的次數在逐漸的減少
(3) 如果我們發現在某趟排序中,沒有發生一次交換, 可以提前結束冒泡排序。這個就是優化
代碼演示(優化后)
/**
* @author zeng
* @version 2019/4/29
* @description: 冒泡排序
*/
public class BubbleSort {
/**
* 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
* 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對,這樣在最后的元素應該會是最大的數;
* 針對所有的元素重復以上的步驟,除了最后一個;
*
* 最佳情況:T(n) = O(n) 最差情況:T(n) = O(n2)
* 最壞情況: O(n2)
* 空間復雜度: O(1)
* 穩定性: 穩定
*
*/
public static void sort(int []a){
int len=a.length;
//臨時變量
int temp = 0;
//標識變量
boolean flag = false;
for(int i=0;i<len;i++){
for(int j=0;j<len-i-1;j++){
if(a[j]>a[j+1]){
flag = true;
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
System.out.println(Arrays.toString(a));
//如果沒有發生變化,表示在改成查詢中一次都沒有交換過,說明已經是有序的,則不需要再遍歷了
if(!flag){
break;
} else {
flag = false;
}
}
}
public static void main(String[] args) {
// int[] a={54,45,32,20,26,43,4,77,3,1,34,21,17,18,8,4,5,8,15,38};
int[] a = {1,2,3,7,6,5};
long currentTimeMillis = System.nanoTime();
System.out.println(currentTimeMillis);
sort(a);
long finishTime = System.nanoTime();
System.out.println(finishTime);
System.out.println("冒泡排序算法時間:"+(finishTime-currentTimeMillis));
System.out.println(Arrays.toString(a));
Arrays.sort(a);
}
}
結果
可以看到在第三次遍歷時,已經已經是有序的,所以不需要在繼續遍歷了。這是優化后的結果。
選擇8000個隨機數,進行排序,獲取結果如下:
時間空間復雜度
由代碼核心循環執行的代碼可知:
時間復雜度為 O(n^2),平均時間 O(n^2).
選擇排序
選擇式排序也屬于內部排序法,是從欲排序的數據中,按指定的規則選出某一元素,再依規定交換位置后達到排序的目的。
基本思想
第一次從arr[0]arr[n-1]中選取最小值,與arr[0]交換,第二次從arr[1]arr[n-1]中選取最小值,與arr[1]交換,第三次從arr[2]arr[n-1]中選取最小值,與arr[2]交換,…,第i次從arr[i-1]arr[n-1]中選取最小值,與arr[i-1]交換,…, 第n-1次從arr[n-2]~arr[n-1]中選取最小值,與arr[n-2]交換,總共通過n-1次,得到一個按排序碼從小到大排列的有序序列。
代碼示例:
public class StraightSelectSort {
/**
* 常用于取序列中最大最小的幾個數時。
*
* (如果每次比較都交換,那么就是交換排序;如果每次比較完一個循環再交換,就是簡單選擇排序。)
*
* 遍歷整個序列,將最小的數放在最前面。
*
* 遍歷剩下的序列,將最小的數放在最前面。
*
* 重復第二步,直到只剩下一個數。
*
* 平均時間復雜度: O(n2)
* * 最好情況: O(n2)
* * 最壞情況: O(n2)
* * 空間復雜度: O(1)
* * 穩定性: 穩定
* *
*/
public static void sort(int []a){
int len=a.length;
int value=0;
int position=0;
for(int i=0;i<len;i++){
value=a[i];
position=i;
int k=i+1;
while(k<len){
// 說明假定的最小值,并不是最小
if(a[k]<value){
//交換
value=a[k];
position=k;
}
k++;
}
// 將最小值,放在arr[0], 即交換,這里也可以判斷一下,是否最小值相等,也可以不做判斷,直接。
if(a[position] != a[i])
a[position]=a[i];
a[i]=value;
}
System.out.println(Arrays.toString(a));
}
public static void main(String[] args) {
// int []a={54,45,32,20,26,43,4,77,3,1,34,21,17,18,8,4,5,8,15,38};
//創建要給80000個的隨機的數組
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
// 生成一個[0, 8000000) 數
arr[i] = (int) (Math.random() * 8000000);
}
long currentTimeMillis = System.nanoTime();
System.out.println(currentTimeMillis);
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的時間是=" + date1Str);
sort(arr);
long finishTime = System.nanoTime();
System.out.println(finishTime);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的時間是=" + date2Str);
System.out.println("簡單選擇排序算法時間:"+(finishTime-currentTimeMillis));
System.out.println(Arrays.toString(arr));
}
}
結果:
8000個隨機數排序結果如下:
時間復雜度
這里使用while,但跟for是差不多的,都是N次。
平均時間復雜度: O(n2)
* * 最好情況: O(n2)
* * 最壞情況: O(n2)
* * 空間復雜度: O(1)
* * 穩定性: 穩定
插入排序
插入式排序屬于內部排序法,是對于欲排序的元素以插入的方式找尋該元素的適當位置,以達到排序的目的。
基本思想
插入排序(Insertion Sorting)的基本思想是:把n個待排序的元素看成為一個有序表和一個無序表,開始時有序表中只包含一個元素,無序表中包含有n-1個元素,排序過程中每次從無序表中取出第一個元素,把它的排序碼依次與有序表元素的排序碼進行比較,將它插入到有序表中的適當位置,使之成為新的有序表。
代碼演示
/**
* 我們經常會到這樣一類排序問題:把新的數據插入到已經排好的數據列中。將第一個數和第二個數排序,
* 然后構成一個有序序列將第三個數插入進去,構成一個新的有序序列。對第四個數、第五個數……
* 直到最后一個數,重復第二步。如題所示:
*
* 直接插入排序(Straight Insertion Sorting)的基本思想:在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,
* 現在要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。如此反復循環,直到全部排好順序。
*
* 假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,
* 即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱為不穩定的。
* n2 代表n的二次方
*
* 平均時間復雜度: O(n2)
* 最好情況: O(n)
* 最壞情況: O(n2)
* 空間復雜度: O(1)
* 穩定性: 穩定
*
*/
public static void sort(int []a){
int length = a.length;
//可知分為兩部分,一部分有序,一部分無序,所以遍歷要從第二個開始
for(int i=1;i<length;i++){
//把要插入的值保存起來
int insertVal = a[i];
int insertIndex = i-1;
//比較大小并替換值
while(insertIndex >= 0 && insertVal < a[insertIndex]){
a[insertIndex+1]=a[insertIndex];
insertIndex--;
}
//把值插入排序好的位置
a[insertIndex+1]=insertVal;
}
}
public static void main(String[] args) {
/*int []a={54,45,32,20,26,43,4,77,3,1,34,21,17,18,8,4,5,8,15,38};
long currentTimeMillis = System.nanoTime();
System.out.println(currentTimeMillis);
sort(a);
long finishTime = System.nanoTime();
System.out.println(finishTime);
System.out.println("直接插入排序算法時間:"+(finishTime-currentTimeMillis));
System.out.println(Arrays.toString(a));*/
//創建要給80000個的隨機的數組
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
}
System.out.println("排序前");
//System.out.println(Arrays.toString(arr));
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的時間是=" + date1Str);
sort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序后的時間是=" + date2Str);
System.out.println(Arrays.toString(arr));
}
}
結果
取8000隨機數
可以看到對比之前的效率高了很多。
時間復雜度
平均時間復雜度: O(n^2)
* 最好情況: O(n)
* 最壞情況: O(n^2)
* 空間復雜度: O(1)
* 穩定性: 穩定
存在的問題
當進行升序排序時,如果最后一個數的值最小,那需要往前移動的次數會很多。效率不高。由此,引出下面的希爾排序。
希爾排序
希爾排序是希爾(Donald Shell)于1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經過改進之后的一個更高效的版本,也稱為縮小增量排序。
基本思想
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。
代碼演示
交換式的shell排序
希爾排序嚴格來說是基于插入排序的思想,又被稱為縮小增量排序。
* 具體流程如下:
* 1、將包含n個元素的數組,分成k=n/2個數組序列,第一個數據和第n/2+1個數據為一對,第二個和第n/2+2為一對,比較...
* 2、對每對數據進行比較和交換,排好順序;
* 3、然后分成n/4個數組序列,再次排序;
* 4、不斷重復以上過程,隨著序列減少并直至為1,排序完成。
*
* 假如有初始數據:25 11 45 26 12 78。
* 1、第一輪排序,將該數組分成 6/2=3 個數組序列,第1個數據和第4個數據為一對,第2個數據和第5個數據為一對,
* 第3個數據和第6個數據為一對,每對數據進行比較排序,排序后順序為:[25, 11, 45, 26, 12, 78]。
* 2、第二輪排序 ,將上輪排序后的數組分成6/4=1個數組序列,此時逐個對數據比較,按照插入排序對該數組進行排序,排序后的順序為:[11, 12, 25, 26, 45, 78]。
*
* 對于插入排序而言,如果原數組是基本有序的,那排序效率就可大大提高。另外,對于數量較小的序列使用直接插入排序,會因需要移動的數據量少,
* 其效率也會提高。因此,希爾排序具有較高的執行效率。
*
* 希爾排序并不穩定,O(1)的額外空間,時間復雜度為O(N*(logN)^2)。
public class ShellSortTest01 {
// 使用逐步推導的方式來編寫希爾排序
// 希爾排序時, 對有序序列在插入時采用交換法,
// 思路(算法) ===> 代碼
public static void sort(int[] arr){
int length = arr.length;
int temp;
// 根據前面的逐步分析,使用循環處理
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
// 遍歷各組中所有的元素(共gap組,每組有個元素), 步長gap
for (int j = i - gap; j >= 0; j -= gap) {
// 如果當前元素大于加上步長后的那個元素,說明交換
if(arr[j]>arr[j+gap]){
temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
// System.out.println(Arrays.toString(arr));
}
}
public static void main(String[] args) {
// int[] arr = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 };
// 創建要給80000個的隨機的數組
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
}
System.out.println("排序前");
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的時間是=" + date1Str);
//shellSort(arr); //交換式
sort(arr);//移位方式
System.out.println(Arrays.toString(arr));
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序后的時間是=" + date2Str);
//System.out.println(Arrays.toString(arr));
}
}
演示結果:
優化后
移位法優化
//對交換式的希爾排序進行優化->移位法
public static void shellSort2(int[] arr) {
// 增量gap, 并逐步的縮小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 從第gap個元素,逐個對其所在的組進行直接插入排序
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
//移動
arr[j] = arr[j-gap];
j -= gap;
}
//當退出while后,就給temp找到插入的位置
arr[j] = temp;
}
}
}
}
演示結果:
同等條件下運行:
可以看到效果由10秒變成了1秒。這是很可觀的。
時間復雜度
希爾排序并不穩定,O(1)的額外空間,時間復雜度為O(N*(logN)^2)
快速排序
介紹
快速排序(Quicksort)是對冒泡排序的一種改進。基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列
步驟:
- 隨機選一個值作為參考值,第一輪,選0,作為參考值
- 分別從左邊和右邊開始輪詢,找到比0比較大的值和比較小的值,進行交換,小的放左邊,大的放右邊,左邊的索引遞增,右邊的遞減,左邊必須大于右邊,如果相等或小于,則退出循環。
- 進行新的一輪循環,第一輪會分為左右兩塊,以0的值作為參考值。采用遞歸,繼續分成兩邊去重復第二部的步驟,直到左邊的索引已經超過要排序的數組的長度了(左邊是遞增的),以及右邊的索引小于要排序的數組的初始索引。
代碼分析
public class QuickSortTest01 {
/**
* 快速排序的基本思想:1、先從數列中取出一個數作為基準數
*
* 2、分區過程,將比這個數大的數全放到它的右邊,小于或等于它的數全放到它的左邊
*
* 3、再對左右區間重復第二步,直到各區間只有一個數.
*平均時間復雜度:
* 最好情況: o(nlogn)
* 最壞情況: o(nlog2n)
* 空間復雜度: o(1)
* 穩定性: 不穩定
*
*/
public static void sort(int []arr,int start,int end){
if(start<end) {
//中軸值
int baseNum = arr[start];
int left = start;
int right = end;
int temp;
do {
//從左邊開始,定位到比中軸值大的數
while (arr[left] < baseNum && left < end) {
left++;
}
//從右邊開始定位到比中軸數,小的數
while (arr[right] > baseNum && right > start) {
right--;
}
//如果左邊的索引小于右邊數的索引,則交換
if (left <= right) {
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
} while (left <= right);
if(left<end){
sort(arr,left,end);
}
if(right> start){
sort(arr,start,right);
}
}
}
public static void main(String[] args) {
int[] arr = {-9,78,0,23,-567,70, -1,900, 4561};
// int[] arr = new int[8000000];
// for (int i = 0; i < 8000000; i++) {
// arr[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
// }
System.out.println("排序前");
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的時間是=" + date1Str);
long currentTimeMillis = System.nanoTime();
sort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
long finishTime = System.nanoTime();
System.out.println("快速排序算法時間:"+(finishTime-currentTimeMillis));
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的時間是=" + date2Str);
}
}
還有另一種寫法,我做過對比
public static void quickSort(int[] arr,int left, int right) {
int l = left; //左下標
int r = right; //右下標
//pivot 中軸值
int pivot = arr[(left + right) / 2];
int temp = 0; //臨時變量,作為交換時使用
//while循環的目的是讓比pivot 值小放到左邊
//比pivot 值大放到右邊
while( l < r) {
//在pivot的左邊一直找,找到大于等于pivot值,才退出
while( arr[l] < pivot) {
l += 1;
}
//在pivot的右邊一直找,找到小于等于pivot值,才退出
while(arr[r] > pivot) {
r -= 1;
}
//如果l >= r說明pivot 的左右兩的值,已經按照左邊全部是
//小于等于pivot值,右邊全部是大于等于pivot值
if( l >= r) {
break;
}
//交換
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交換完后,發現這個arr[l] == pivot值 相等 r--, 前移
if(arr[l] == pivot) {
r -= 1;
}
//如果交換完后,發現這個arr[r] == pivot值 相等 l++, 后移
if(arr[r] == pivot) {
l += 1;
}
}
// 如果 l == r, 必須l++, r--, 否則為出現棧溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左遞歸
if(left < r) {
quickSort(arr, left, r);
}
//向右遞歸
if(right > l) {
quickSort(arr, l, right);
}
}
結果對比
第一種代碼的結果
第二種代碼的結果
快了百分之50以上,建議使用第一種,還有優化的可以私聊告訴我。總體的思路是一樣的。
時間復雜度
歸并算法
介紹
歸并排序(MERGE-SORT)是利用歸并的思想實現的排序方法,該算法采用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然后遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。
可以看到這種結構很像一棵完全二叉樹,本文的歸并排序我們采用遞歸去實現(也可采用迭代的方式去實現)。分階段可以理解為就是遞歸拆分子序列的過程。
歸并排序思想示意圖1-基本思想:
歸并排序思想示意圖2-合并相鄰有序子序列:
再來看看治階段,我們需要將兩個已經有序的子序列合并成一個有序序列,比如上圖中的最后一次合并,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合并為最終序列[1,2,3,4,5,6,7,8],來看下實現步驟
代碼演示:
public class MergeSort {
/**
* Merge sort
* 速度僅次于快速排序,內存少的時候使用,可以進行并行計算的時候使用。
* 選擇相鄰兩個數組成一個有序序列。
* 選擇相鄰的兩個有序序列組成一個有序序列。
* 重復第二步,直到全部組成一個有序序列。
*
* 最佳情況:T(n) = O(n) 最差情況:T(n) = O(n2)
* 最壞情況: O(n2)
* 空間復雜度: O(1)
* 穩定性: 穩定
*
*/
public static void mergeSort(int []arr,int left,int right,int[] temp){
if(left < right) {
int mid = (left+right) / 2;
mergeSort(arr,left,mid,temp);
mergeSort(arr,mid+1,right,temp);
merge(arr,left,mid,right,temp);
}
}
public static void merge(int[] arr,int left,int mid,int right,int[] temp){
// 初始化i左邊有序序列得初始索引
int i=left;
// 初始化j右邊有序序列初始索引
int j=mid+1;
//temp的初識索引
int t=0;
// 將兩邊的索引進行排序
while(i<=mid && j<= right){
if(arr[i] <= arr[j]){
temp[t++]=arr[i];
i++;
} else{
temp[t++]=arr[j];
j++;
}
}
//將兩邊剩余的值加入temp的值
while(i<=mid){
temp[t++]=arr[i];
i++;
}
while(j <= right){
temp[t++]=arr[j];
j++;
}
//把temp中的值加入arr中。
int templeft=left;
int t1=0;
while(templeft <= right){
arr[templeft]=temp[t1++];
templeft++;
}
}
public static void main(String[] args) {
// int a[] = { 8, 4, 5, 7, 1, 3, 6, 2 };
// int []a={54,45,32,20,26,43,4,77,3,1,34,21,17,18,8,4,5,8,15,38};
// long currentTimeMillis = System.nanoTime();
// System.out.println(currentTimeMillis);
// mergeSort(a);
// long finishTime = System.nanoTime();
// System.out.println(finishTime);
// System.out.println("歸并排序算法時間:"+(finishTime-currentTimeMillis));
// System.out.println(Arrays.toString(a));
//int arr[] = { 8, 4, 5, 7, 1, 3, 6, 2 }; //
//測試快排的執行速度
// 創建要給80000個的隨機的數組
int[] a = new int[8000000];
for (int i = 0; i < 8000000; i++) {
a[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
}
System.out.println("排序前");
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的時間是=" + date1Str);
int temp[] = new int[a.length]; //歸并排序需要一個額外空間
long currentTimeMillis = System.nanoTime();
System.out.println(currentTimeMillis);
mergeSort(a, 0, a.length - 1, temp);
long finishTime = System.nanoTime();
System.out.println(finishTime);
System.out.println("歸并排序算法時間:"+(finishTime-currentTimeMillis));
System.out.println(Arrays.toString(a));
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的時間是=" + date2Str);
//System.out.println("歸并排序后=" + Arrays.toString(arr));
}
}
結果演示:
時間復雜度
- 最佳情況:T(n) = O(n) 最差情況:T(n) = O(n2)
最壞情況: O(n2)
空間復雜度: O(1)
穩定性: 穩定
基數排序
基數排序(桶排序)介紹
基數排序(radix sort)屬于“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是通過鍵值的各個位的值,將要排序的元素分配至某些“桶”中,達到排序的作用
基數排序法是屬于穩定性的排序,基數排序法的是效率高的穩定性排序法
基數排序(Radix Sort)是桶排序的擴展
基數排序是1887年赫爾曼·何樂禮發明的。它是這樣實現的:將整數按位數切割成不同的數字,然后按每個位數分別比較。
基本思想
將所有待比較數值統一為同樣的數位長度,數位較短的數前面補零。然后,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以后, 數列就變成一個有序序列。
看一個圖文解釋,理解基數排序的步驟
(數的位數達不到個位以上的,在每次桶排序輪空后,依次加入第一個桶)。
代碼演示:
public class RadixSort {
public static void main(String[] args) {
int arr[] = { 53, 3, 542, 748, 14, 214};
// 80000000 * 11 * 4 / 1024 / 1024 / 1024 =3.3G
// int[] arr = new int[8000000];
// for (int i = 0; i < 8000000; i++) {
// arr[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
// }
System.out.println("排序前");
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的時間是=" + date1Str);
radixSort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的時間是=" + date2Str);
System.out.println("基數排序后 " + Arrays.toString(arr));
}
//基數排序方法
public static void radixSort(int[] arr) {
//根據前面的推導過程,我們可以得到最終的基數排序代碼
//1. 得到數組中最大的數的位數
int max = arr[0]; //假設第一數就是最大數
for(int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大數是幾位數
int maxLength = (max + "").length();
//定義一個二維數組,表示10個桶, 每個桶就是一個一維數組
//說明
//1. 二維數組包含10個一維數組
//2. 為了防止在放入數的時候,數據溢出,則每個一維數組(桶),大小定為arr.length
//3. 名明確,基數排序是使用空間換時間的經典算法
int[][] bucket = new int[10][arr.length];
//為了記錄每個桶中,實際存放了多少個數據,我們定義一個一維數組來記錄各個桶的每次放入的數據個數
//可以這里理解
//比如:bucketElementCounts[0] , 記錄的就是 bucket[0] 桶的放入數據個數
int[] bucketElementCounts = new int[10];
//這里我們使用循環將代碼處理
for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
//(針對每個元素的對應位進行排序處理), 第一次是個位,第二次是十位,第三次是百位..
for(int j = 0; j < arr.length; j++) {
//取出每個元素的對應位的值
int digitOfElement = arr[j] / n % 10;
//放入到對應的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照這個桶的順序(一維數組的下標依次取出數據,放入原來數組)
int index = 0;
//遍歷每一桶,并將桶中是數據,放入到原數組
for(int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有數據,我們才放入到原數組
if(bucketElementCounts[k] != 0) {
//循環該桶即第k個桶(即第k個一維數組), 放入
for(int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
//第i+1輪處理后,需要將每個 bucketElementCounts[k] = 0 !!!!
bucketElementCounts[k] = 0;
}
//System.out.println("第"+(i+1)+"輪,對個位的排序處理 arr =" + Arrays.toString(arr));
}
}
}
運行結果
補充說明
基數排序是對傳統桶排序的擴展,速度很快.
基數排序是經典的空間換時間的方式,占用內存很大, 當對海量數據排序時,容易造成 OutOfMemoryError 。
基數排序時穩定的。[注:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱為不穩定的]
有負數的數組,我們不用基數排序來進行排序, 如果要支持負數,參考:
https://code.i-harness.com/zh-CN/q/e98fa9
時間復雜度
堆排序算法
http://www.lxweimin.com/p/51bbf3717493
待續