我親愛的
第七章 排序算法
文章目錄
1. 排序算法的介紹
排序也稱排序算法(Sort Algorithm),排序是將一 組數據,依指定的順序進行排列
的過程。
1.1排序的分類
1) 內部排序:
指將需要處理的所有數據都加載到內部存儲器中進行排序。
2) 外部排序法:
數據量過大,無法全部加載到內存中,需要藉助外部存儲進行排序。
3) 常見的排序算法分類
2. 算法的時間複雜度
2.1 度量一個程序(算法)執行時間的兩種方法.
1) 事後統計的方法
這種方法可行, 但是有兩個問題:一是要想對設計的算法的運行性能進行評測,需要實際運行該程序;二是所得時間的統計量依賴於計算機的硬件、軟件等環境因素, 這種方式,要在同一臺計算機的相同狀態下運行,才能比較那個算法速度更快。
2) 事前估算的方法
通過分析某個算法的時間複雜度來判斷哪個算法更優.
2. 時間頻度
時間頻度:一個算法花費的時間與算法中語句的執行次數成正比例,哪個算法中語句執行次數多,它花費時間就多。一個算法中的語句執行次數稱爲語句頻度或時間頻度。記爲T(n)。
忽略常數項 | T(n)=2n+20 | T(n)=2*n | T(3n+10) | T(3n) |
---|---|---|---|---|
1 | 22 | 2 | 13 | 3 |
2 | 24 | 4 | 16 | 6 |
5 | 30 | 10 | 25 | 15 |
8 | 36 | 16 | 34 | 24 |
15 | 50 | 30 | 55 | 45 |
30 | 80 | 60 | 100 | 90 |
100 | 220 | 200 | 310 | 300 |
300 | 620 | 600 | 910 | 900 |
結論:
1.2n+20 和 2n 隨着n 變大,執行曲線無限接近, 20可以忽略
2.3n+10 和 3n 隨着n 變大,執行曲線無限接近, 10可以忽略
忽略低次項 | T(n)=2n^2+3n+10 | T(2n^2) | T(n^2+5n+20) | T(n^2) |
---|---|---|---|---|
1 | 15 | 2 | 26 | 1 |
2 | 24 | 8 | 34 | 4 |
5 | 75 | 50 | 70 | 25 |
8 | 162 | 128 | 124 | 64 |
15 | 505 | 450 | 320 | 225 |
30 | 1900 | 1800 | 1070 | 900 |
100 | 20310 | 20000 | 10520 | 10000 |
結論:
1.2n^2+3n+10 和 2n^2 隨着n 變大, 執行曲線無限接近, 可以忽略 3n+10
2.n^2+5n+20 和 n^2 隨着n 變大,執行曲線無限接近, 可以忽略 5n+20
忽略係數 | T(3n^2+2n) | T(5n^2+7n) | T(n^3+5n) | T(6n^3+4n) |
---|---|---|---|---|
1 | 5 | 12 | 6 | 10 |
2 | 16 | 34 | 18 | 56 |
5 | 85 | 160 | 150 | 770 |
8 | 208 | 376 | 552 | 3104 |
15 | 705 | 1230 | 3450 | 20310 |
30 | 2760 | 4710 | 27150 | 162120 |
100 | 30200 | 50700 | 1000500 | 6000400 |
結論:
1.隨着n值變大,5n^2+7n 和 3n^2 + 2n ,執行曲線重合, 說明 這種情況下, 5和3可以忽略。
2.而n^3+5n 和 6n^3+4n ,執行曲線分離,說明多少次方式關鍵
3.時間複雜度
1.一般情況下,算法中的基本操作語句的重複執行次數是問題規模n的某個函數,用T(n)表示,若有某個輔助函數f(n),使得當n趨近於無窮大時,T(n) / f(n) 的極限值爲不等於零的常數,則稱f(n)是T(n)的同數量級函數。記作 T(n)=O( f(n) ),稱O( f(n) ) 爲算法的漸進時間複雜度,簡稱時間複雜度。
2. T(n) 不同,但時間複雜度可能相同。 如:T(n)=n²+7n+6 與 T(n)=3n²+2n+2 它們的T(n) 不同,但時間複雜度相同,都爲O(n²)。
計算時間複雜度的方法:
- 用常數1代替運行時間中的所有加法常數 T(n)=n²+7n+6 => T(n)=n²+7n+1
- 修改後的運行次數函數中,只保留最高階項 T(n)=n²+7n+1 => T(n) = n²
- 去除最高階項的係數 T(n) = n² => T(n) = n² => O(n²)
3. 常見的時間複雜度
1.常數階O(1)
2.對數階O(log2n)
3.線性階O(n)
4.線性對數階O(nlog2n)
5.平方階O(n^2)
6.立方階O(n^3)
7.k次方階O(n^k)
8.指數階O(2^n)
說明:
1.常見的算法時間複雜度由小到大依次爲:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)< Ο(n^k) <Ο(2n)
2.隨着問題規模n的不斷增大,上述時間複雜度不斷增大,算法的執行效率越低
從圖中可見,我們應該儘可能避免使用指數階的算法
4.平均時間複雜度和最壞時間複雜度
1.平均時間複雜度是指所有可能的輸入實例均以等概率出現的情況下,該算法的運行時間。
2.最壞情況下的時間複雜度稱最壞時間複雜度。一般討論的時間複雜度均是最壞情況下的時間複雜度。 這樣做的原因是:最壞情況下的時間複雜度是算法在任何輸入實例上運行時間的界限,這就保證了算法的運行時間不會比最壞情況更長。
3.平均時間複雜度和最壞時間複雜度是否一致,和算法有關
3.算法的空間複雜度簡介
1.類似於時間複雜度的討論,一個算法的空間複雜度(Space Complexity)定義爲該算法所耗費的存儲空間,它也是問題規模n的函數。
2.空間複雜度(Space Complexity)是對一個算法在運行過程中臨時佔用存儲空間大小的量度。有的算法需要佔用的臨時工作單元數與解決問題的規模n有關,它隨着n的增大而增大,當n較大時,將佔用較多的存儲單元,例如快速排序和歸併排序算法就屬於這種情況
3.在做算法分析時,主要討論的是時間複雜度。從用戶使用體驗上看,更看重的程序執行的速度。一些緩存產品(redis, memcache)和算法(基數排序)本質就是用空間換時間.
4 .冒泡排序
1.冒泡排序(Bubble Sorting)的基本思想是:通過對待排序序列從前向後(從下標較小的元素開始),
依次比較相鄰元素的值,若發現逆序則交換,使值較大的元素逐漸從前移向後部,就象水底下的氣泡一樣逐漸
向上冒。
2.因爲排序的過程中,各元素不斷接近自己的位置,如果一趟比較下
來沒有進行過交換,就說明序列有序,因此要在排序過程中設置
一個標誌flag判斷元素是否進行過交換。從而減少不必要的比較。(這裏說的優化,可以在冒泡排序寫好後,在進行)
4.1 冒泡排序應用實例
我們將五個無序的數:3, 9, -1, 10, -2 使用冒泡排序法將其排成一個從小到大的有序數列。
4.2 代碼實現
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class BubbleSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
// System.out.println("hello world");
// int arr[] = {3,9,-1,10,20};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
//測試一下冒泡排序的速度O(n^2), 給80000個數據,測試
//創建要給80000個的隨機的數組
int[] arr = new int[80000];
for(int i = 0;i < 80000;i++) {
arr[i] = (int)(Math.random() * 8000000); //生成一個[0, 8000000) 數
}
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy--MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的時間是=" + date1Str);
//測試冒泡排序
bubbleSort(arr);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序後的時間是=" + date2Str);
// System.out.println("排序後");
// System.out.println(Arrays.toString(arr));
}
public static void bubbleSort(int[] arr) {
// 冒泡排序 的時間複雜度 O(n^2),
int temp = 0;// 臨時變量
boolean flag = false;// 標識變量,表示是否進行過交換
for(int i = 0;i < arr.length-1;i++) {
for(int j = 0;j < arr.length - 1 - i;j++) {
// 如果前面的數比後面的數大,則交換
if(arr[j]>arr[j+1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j+1];
arr[j + 1] = temp;
}
}
// System.out.println("第" + (i + 1) + "趟排序後的數組");
// System.out.println(Arrays.toString(arr));
if(!flag) { // 在一趟排序中,一次交換都沒有發生過
break;
}else {
flag = false; // 重置flag!!!, 進行下次判斷
}
}
}
}
5. 選擇排序
1.選擇式排序也屬於內部排序法,是從欲排序的數據中,按指定的規則選出某一元素,再依規定交換位置後達到排序的目的。
5.1 選擇排序思想
1.選擇排序(select sorting)也是一種簡單的排序方法。它的基本思想是:
2.第一次從arr[0]~arr[n-1]中選取最小值,與arr[0]交換,
3.第二次從arr[1]~arr[n-1]中選取最小值,與arr[1]交換,
4.第三次從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次,得到一個按排序碼從小到大排列的有序序列。
5.2 選擇排序應用實例
有一羣牛 , 顏值分別是 101, 34, 119, 1 請使用選擇排序從低到高進行排序 [101, 34, 119, 1]
5.3 代碼實現
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class SelectSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
// int [] arr = {101, 34, 119, 1, -1, 90, 123};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
//
// selectSort(arr);
// System.out.println("排序後");
// System.out.println(Arrays.toString(arr));
//創建要給80000個的隨機的數組
int[] arr = new int[80000];
for(int i = 0;i < 80000;i++) {
arr[i] = (int)(Math.random() * 8000000); //生成一個[0, 8000000) 數
}
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy--MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的時間是=" + date1Str);
selectSort(arr);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序後的時間是=" + date2Str);
// System.out.println(Arrays.toString(arr));
}
public static void selectSort(int[] arr) {
//選擇排序時間複雜度是 O(n^2)
for(int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
for(int j = i + 1; j < arr.length; j++) {
if(min > arr[j]) { // 說明假定的最小值,並不是最小
min = arr[j]; // 重置min
minIndex = j; // 重置minIndex
}
}
// 將最小值,放在arr[0], 即交換
if(minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
}
6.插入排序
插入式排序屬於內部排序法,是對於欲排序的元素以插入的方式找尋該元素的適當位置,以達到排序的目的。
6.1 插入排序法思想
插入排序(Insertion Sorting)的基本思想是:
1.把n個待排序的元素看成爲一個有序表和一個無序表,
2.開始時有序表中只包含一個元素,無序表中包含有n-1個元素,
3.排序過程中每次從無序表中取出第一個元素,把它的排序碼依次與有序表元素的排序碼進行比較,
4.將它插入到有序表中的適當位置,使之成爲新的有序表。
6.2 插入排序法應用實例
有一羣小牛, 考試成績分別是 101, 34, 119, 1 請從小到大排序.
6.3 代碼實現
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class InsertSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
// int[] arr = {101, 34, 119, 1, -1, 89};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
//
// insertSort(arr);
// System.out.println("排序後");
// System.out.println(Arrays.toString(arr));
//創建要給80000個的隨機的數組
int[] arr = new int[80000];
for(int i = 0;i < 80000;i++) {
arr[i] = (int)(Math.random() * 8000000); //生成一個[0, 8000000) 數
}
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy--MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的時間是=" + date1Str);
insertSort(arr);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序後的時間是=" + date2Str);
}
public static void insertSort(int[] arr) {
int insertVal = 0;
int insertIndex = 0;
//使用for循環來把代碼簡化
for(int i = 1; i < arr.length; i++) {
//定義待插入的數
insertVal = arr[i];
insertIndex = i - 1; // 即arr[i]的前面這個數的下標
// 給insertVal 找到插入的位置
// 說明
// 1. insertIndex >= 0 保證在給insertVal 找插入位置,不越界
// 2. insertVal < arr[insertIndex] 待插入的數,還沒有找到插入位置
// 3. 就需要將 arr[insertIndex] 後移 (往下標減小方向移動)
while(insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 當退出while循環時,說明插入的位置找到, insertIndex + 1
//這裏我們判斷是否需要賦值
if(insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
}
}
}
7. 希爾排序
1.希爾排序是希爾(Donald Shell)於1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經過改進之後的一個更高效的版本,也稱爲縮小增量排序。
7.1 希爾排序法基本思想
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止.
7.1 希爾排序法應用實例
有一羣小牛, 考試成績分別是 {8,9,1,7,2,3,5,4,6,0} 請從小到大排序. 請分別使用 :
7.1 希爾排序時, 對有序序列在插入時採用交換法, 並測試排序速度
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class ShellSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
//
// int[] arr = {8,9,1,7,2,3,5,4,6,0};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
// shellSort(arr);
// System.out.println("排序後");
// System.out.println(Arrays.toString(arr));
// 創建要給80000個的隨機的數組
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random() * 8000000);
}
System.out.println("排序前");
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前: " + date1Str);
shellSort(arr);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序後: " + date2Str);
}
// 希爾排序時, 對有序序列在插入時採用交換法,
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
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;
}
}
}
}
}
}
7.2 希爾排序時, 對有序序列在插入時採用移動法, 並測試排序速度
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class ShellSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
//
// int[] arr = {8,9,1,7,2,3,5,4,6,0};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
// shellSort(arr);
// System.out.println("排序後");
// System.out.println(Arrays.toString(arr));
// 創建要給80000個的隨機的數組
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random() * 8000000);
}
System.out.println("排序前");
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前: " + date1Str);
// shellSort(arr);
shellSort2(arr);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序後: " + date2Str);
}
// 希爾排序時, 對有序序列在插入時採用交換法,
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
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;
}
}
}
}
}
//對交換式的希爾排序進行優化->移位法
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;
}
}
}
}
}
8.快速排序
8.1快速排序法介紹
快速排序(Quicksort)是對冒泡排序的一種改進。基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列
8.2 快速排序法應用實例
對 [-9,78,0,23,-567,70] 進行從小到大的排序,要求使用快速排序法。
8.3 代碼實現
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class QuickSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
// int[] arr = {-9,78,0,23,-567,70, -1,900, 4561};
//測試快排的執行速度
// 創建要給80000個的隨機的數組
int[] arr = new int[8000000];
for (int i = 0; i < 8000000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
}
System.out.println("排序前");
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的時間是=" + date1Str);
quickSort(arr, 0, arr.length-1);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序後的時間是=" + date2Str);
// System.out.println("arr=" + Arrays.toString(arr));
}
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);
}
}
}
9.歸併排序
9.1 歸併排序介紹
歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然後遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。
9.2 歸併排序思想示意圖合併相鄰有序子序列
再來看看治階段,我們需要將兩個已經有序的子序列合併成一個有序序列,比如上圖中的最後一次合併,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合併爲最終序列[1,2,3,4,5,6,7,8],來看下實現步驟.
9.3 歸併排序的應用實例
給你一個數組, val arr = Array(9,8,7,6,5,4,3,2,1), 請使用歸併排序完成排序。
9.4 代碼實現
import java.text.SimpleDateFormat;
import java.util.Date;
public class MergetSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
//int arr[] = { 8, 4, 5, 7, 1, 3, 6, 2 }; //
//測試快排的執行速度
// 創建要給80000個的隨機的數組
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);
int temp[] = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的時間是=" + date2Str);
}
//分+合方法
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);
//合併
}
}
//合併的方法
/**
*
* @param arr 排序的原始數組
* @param left 左邊有序序列的初始索引
* @param mid 中間索引
* @param right 右邊索引
* @param temp 做中轉的數組
*/
public static void merge(int[] arr,int left,int mid,int right,int[] temp) {
int i = left; // 初始化i, 左邊有序序列的初始索引
int j = mid + 1; //初始化j, 右邊有序序列的初始索引
int t = 0; // 指向temp數組的當前索引
//(一)
//先把左右兩邊(有序)的數據按照規則填充到temp數組
//直到左右兩邊的有序序列,有一邊處理完畢爲止
while(i <= mid && j <= right) {
//如果左邊的有序序列的當前元素,小於等於右邊有序序列的當前元素
//即將左邊的當前元素,填充到 temp數組
//然後 t++, i++
if(arr[i] <= arr[j]) {
temp[t] = arr[i];
t += 1;
i += 1;
}else { //反之,將右邊有序序列的當前元素,填充到temp數組
temp[t] = arr[j];
t += 1;
j += 1;
}
}
//(二)
//把有剩餘數據的一邊的數據依次全部填充到temp
while(i <= mid) { //左邊的有序序列還有剩餘的元素,就全部填充到temp
temp[t] = arr[i];
t += 1;
i += 1;
}
while(j <= right) { //右邊的有序序列還有剩餘的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
//(三)
//將temp數組的元素拷貝到arr
//注意,並不是每次都拷貝所有
t = 0;
int tempLeft = left;
//第一次合併 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最後一次 tempLeft = 0 right = 7
while(tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
10.基數排序
10.1 基數排序(桶排序)介紹
1.基數排序(radix sort)屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是通過鍵值的各個位的值,將要排序的元素分配至某些“桶”中,達到排序的作用
2.基數排序法是屬於穩定性的排序,基數排序法的是效率高的穩定性排序法
3.基數排序(Radix Sort)是桶排序的擴展
4.基數排序是1887年赫爾曼·何樂禮發明的。它是這樣實現的:將整數按位數切割成不同的數字,然後按每個位數分別比較。
10.2 基數排序基本思想
將所有待比較數值統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。
10.3基數排序圖文說明
將數組 {53, 3, 542, 748, 14, 214} 使用基數排序, 進行升序排序。
10.3.1 第1輪排序 [按照個位排序]:
10.3.2 第2輪排序 [按照十位排序]:
10.3.3 第3輪排序 [按照百位排序]:
10.4 基數排序代碼實現
要求:將數組 {53, 3, 542, 748, 14, 214 } 使用基數排序, 進行升序排序
10.4 代碼實現:
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class RadixSort {
public static void main(String[] args) {
int arr[] = {53,3,542,765,12,214};
System.out.println("排序前");
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
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[index++] = bucket[k][l];
}
}
//第i+1輪處理後,需要將每個 bucketElementCounts[k] = 0 !
bucketElementCounts[k] = 0;
}
}
}
}
10.5 基數排序的說明
1.基數排序是對傳統桶排序的擴展,速度很快.
2.基數排序是經典的空間換時間的方式,佔用內存很大, 當對海量數據排序時,容易造成 OutOfMemoryError 。
3.基數排序時穩定的。[注:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序後的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱爲不穩定的]
4.有負數的數組,一般不用基數排序來進行排序.
常用排序算法總結和對比
相關術語解釋:
1.穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面;
2.不穩定:如果a原本在b的前面,而a=b,排序之後a可能會出現在b的後面;
3.內排序:所有排序操作都在內存中完成;
4.外排序:由於數據太大,因此把數據放在磁盤中,而排序通過磁盤和內存的數據傳輸才能進行;
5.時間複雜度: 一個算法執行所耗費的時間。
6.空間複雜度:運行完一個程序所需內存的大小。
7.n: 數據規模
8.k: “桶”的個數
9.In-place: 不佔用額外內存
10.Out-place: 佔用額外內存