第七章 排序(SORT)算法


我親愛的


第七章 排序算法

文章目錄

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+202n 隨着n 變大,執行曲線無限接近, 20可以忽略
2.3n+103n 隨着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+102n^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

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AKnay0bS-1590679263116)(C:\Users\LFY\AppData\Roaming\Typora\typora-user-images\image-20200516210547564.png)]

結論:

1.隨着n值變大,5n^2+7n 和 3n^2 + 2n ,執行曲線重合, 說明  這種情況下, 53可以忽略。
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)=( f(n) ),稱O( f(n) )  爲算法的漸進時間複雜度,簡稱時間複雜度。

2. T(n) 不同,但時間複雜度可能相同。 如:T(n)=+7n+6T(n)=3+2n+2 它們的T(n) 不同,但時間複雜度相同,都爲O()

計算時間複雜度的方法:

  • 用常數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],來看下實現步驟.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-37CKstPJ-1590679263129)(C:\Users\LFY\AppData\Roaming\Typora\typora-user-images\image-20200523094234567.png)]

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.有負數的數組,一般不用基數排序來進行排序.

常用排序算法總結和對比

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eI869kW8-1590679263135)(C:\Users\LFY\AppData\Roaming\Typora\typora-user-images\image-20200523095526885.png)]

相關術語解釋:

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: 佔用額外內存
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章