常見的八種排序算法

排序按類別分爲:插入排序(插入排序、希爾排序)、選擇排序(選擇排序、堆排序)、交換排序(冒泡排序、快速排序)、歸併排序、基數排序。

1. 冒泡排序

/**
	 * 從第一個數據開始,將順序表中的每一個數據與其後一個數據比較大小 如果比後邊大,就交換位置, 直到倒數第二個與倒數第一個比較完畢
	 * 我們可以得到最大的數,放在隊尾, 不斷循環執行,直到數組有序
	 */
	public static void bubbleSort(int[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = 0; j < arr.length - i - 1; j++) {
				if (arr[j] > arr[j + 1]) {
					int temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
		}
	}

2. 選擇排序

/**
	 * 選擇數列中下表爲0的數作爲index,將其後面的數與index指向的數作比較
	 * 若後面的數比index指向的數小,就把index指向後面的數,直到比較到數列尾,
	 * 此時指向數列中最小的數,然後選擇下標爲1的數作爲index,繼續進行選擇排序,直到結束
	 */
	public static void selectSort(int[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = i + 1; j < arr.length; j++) {
				if (arr[i] > arr[j]) {
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
				}
			}
		}
	}

3. 歸併排序

/**
	 * 將一個數列從中間分爲兩個有序的數列,然後再將兩個數列按照從小到大的順序合併成一個有序數列
	 * 時間複雜度:O(nlogn)
	 * 空間複雜度:O(N),歸併排序需要一個與原數組相同長度的數組做輔助
	 * 
	 * 穩定性:歸併排序是最穩定的排序算法,
	 * 當左右部分值相等時,
	 * 先複製左邊的值,保證兩個值相等的元素的相對位置不變
	 */
	public static void mergeSort(int arr[], int left, int right){
		if(left == right){
			return;
		}else{
			int mid = (left+right)/2;
    			mergeSort(arr, left, mid);
			mergeSort(arr, mid+1, right);
			merge(arr, left, mid, right);
		}
	}
	public static void merge(int[] arr, int left, int mid, int right){
		int[] tmp = new int[right - left + 1];
		int i = 0;
		int p1 = left;
		int p2 = mid+1;
//		比較左右兩部分元素,那個小,把那個元素填入tmp中
		while(p1 <= mid && p2 <= right){
			tmp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
//		在上面循環退出後,把剩餘元素依次填入到tmp中
		while(p1 <= mid){
			tmp[i++] = arr[p1++];
		}
		while(p2 <= right){
			tmp[i++] = arr[p2++];
		}
//		把最終排序結果賦值給原數組
		for(i = 0; i < tmp.length; i++){
			arr[left + i] = tmp[i];
		}
	}

4. 快速排序

/**
	 * 在數列中選擇一個數作爲基準,用其他的數與該數進行比較, 比他大的放右邊,比他小的放左邊 這樣一輪比較完成後,左邊的數都比它小,右邊的數都比它大
	 * 以該數爲中間點,將數列分成兩部分, 分別進行同樣的操作,直到數列有序
	 */
	public static void quickSort(int[] arr, int low, int high) {

		if (low < high) {
			int index = getIndex(arr, low, high);
			quickSort(arr, 0, index - 1);
			quickSort(arr, index + 1, high);
		}
	}

	private static int getIndex(int[] arr, int low, int high) {
		// 基準數據
		int tmp = arr[low];
		while (low < high) {
			// 當隊尾的元素大於等於基準數據時,向前挪動high指針
			while (low < high && arr[high] >= tmp) {
				high--;
			}
			// 如果隊尾元素小於tmp了,需要將其值賦給low
			arr[low] = arr[high];
			// 當隊首元素小於等於tmp時,向前挪動low指針
			while (low < high && arr[low] <= tmp) {
				low++;
			}
			arr[high] = arr[low];
		}
//		跳出循環時low和high相等,此時的low或high就是tmp的正確索引位置
		arr[low] = tmp;
		return low;
	}

5. 鏈式基數排序

基數排序採用“分配”與“收集”的辦法,用對多關鍵碼進行排序的思想實現對單關鍵碼進行排序的方法。

思路:使用鏈表存儲數據,通過調整指針實現LSD(低關鍵字優先)算法進行排序。

舉個例子就是我們在qq遊戲上的紙牌或麻將,從服務器端返回來一組數據,紙牌和麻將都是多關鍵字的對象,紙牌和麻將都是有點數和花色劃分的,我們要對這組數據進行排序,那使用鏈式基數排序效率是最快的。

而且這種排序是使用在對原始數據的排序,如果有新數據添加進來的話,那就使用希爾排序。

步驟:

(1)先初始化一個要存放的點數的集合數組,根據點數把數據放入對應的數組中,把點數爲1的數放入數組爲0的下標中, 點數爲2的數據放入1的下標中,數組中每個下標存放數據的都是一個集合,這樣就可以把數值爲1~9的麻將分別存入對應的數組中。然後再把數組中的集合按順序依次添加到一個新的集合中。

(2)初始化一個存放花色的集合數組,根據花色把數據放入對應的組中,把花色爲萬字的數據放入數組爲0的下標中,花色爲條字的數據放入數組爲1的下標中,花色爲筒字的數據放入數組爲2的下標中,這樣就可以把花色爲萬筒條的數據都存入對應的數組中。最後再把數組中的集合桉樹按順序依次添加到一個新的集合當中。

 

/**
	 * 鏈式基數排序
	 * 例如給麻將排序,
	 * 先按照點數大小排成一個鏈表,然後根據類別,分成三個鏈表
	 * 分別按照大小順序存放不同類別的麻將
	 * 最後將這三個鏈表連接起來,得到最終排好序的麻將結果
	 * 
	 * 使用場景:
	 * 多關鍵字排序,只適合十幾個數據以內,數據量不大的情況
	 * 鬥地主紙牌遊戲, 麻將初始化排序
	 */
	public static class Mahjong{
		public int suit; //花色
		public int rank; //點數
		public Mahjong(int suit, int rank){
			this.suit = suit;
			this.rank = rank;
		}
		@Override
		public String toString() {
			return "Mahjong [suit=" + suit + ", rank=" + rank + "]";
		}
	}
	public static void radixSort(LinkedList<Mahjong> list){
//		先初始化要存放點數的集合數組
		LinkedList[] rankList = new LinkedList[9];
		for(int i = 0; i < rankList.length; i++){
			rankList[i] = new LinkedList<>();//讓每個數組都放置鏈表集合
		}
//		將數據依次放入對應組中
		while(list.size() > 0){
			Mahjong m = list.remove();
//			放入組中,下標爲點數減一
			rankList[m.rank-1].add(m); // 麻將點數減一爲放入的數組下標
		}
//		把9個數組數據連接起來,形成一個鏈表
		for(int i = 0; i < rankList.length; i++){
			list.addAll(rankList[i]);
		}
//		進行花色分組
		LinkedList[] suitList = new LinkedList[3];
		for(int i = 0; i < suitList.length; i++){
			suitList[i] = new LinkedList<>();
		}
//		把數據一個個放入組中
		while(list.size() > 0){
			Mahjong m = list.remove();
//			放入組中,下標爲點數減一
			suitList[m.suit-1].add(m);
		}
		for(int i = 0; i < suitList.length; i++){
			list.addAll(suitList[i]);
		}
	}
	public static void main(String[] args) {
		LinkedList<Mahjong> list = new LinkedList<>();
		list.add(new Mahjong(3, 1));
		list.add(new Mahjong(2, 3));
	    list.add(new Mahjong(3, 7));
	    list.add(new Mahjong(1, 1));
	    list.add(new Mahjong(3, 8));
	    list.add(new Mahjong(2, 2));
	    list.add(new Mahjong(3, 2));
	    list.add(new Mahjong(1, 3));
	    list.add(new Mahjong(3, 9));
	    radixSort(list);
	}

6. 插入排序

/**
	 * 插入排序
	 * 思路:
	 * 從數列的第一個數開始,將其與前面的數進行比較,如果比前面的小,就插入到前面的位置
	 * 直到比較到第零個數到達數列頭部
	 * 應用場景:
	 * 主要爲希爾排序做準備
	 * 打牌摸牌時的場景
	 */
	public static void insertSort(int[] arr){
		for(int i = 1; i < arr.length; i++){
			int j = i;
			int target = arr[j];
			while(j > 0 && target < arr[j-1]){
				arr[j] = arr[j-1];
				j--;
			}
			arr[j] = target;
		} 
	} 

7. 希爾排序

步驟:

1)首先把較大的數據集合分割成若干個小組(邏輯上分組),然後對每一個小組分別進行插入排序,此時排序所作用的數據量比較小(每個小組),插入的效率比較高。

2)通過增量進行分組,如增量爲4,則a[0]與a[4]一組、a[1]與a[5]一組。

3)每個分組進行插入排序後,各分組就變成有序的了,此時整個數組部分有序。

4)然後縮小增量爲上一個增量的一半:2,繼續劃分分組。雖然每個分組的元素個數多了,但由於數組變得部分有序,所以插入效率同樣比較高。

5)直到最後增量爲1,整個數組被分爲一組,在進行插入排序;此時數組已經接近有序,所以插入效率很高。

特點:

希爾排序靈活的運用了插入排序適用於數據量小且越有序排序效率越高的特點;其同時構造出兩個特殊條件已達到高校插入,使得希爾排序升級成爲可以運用在數據量較大,且數組無序的場景下。

複雜度:

希爾排序的複雜度和增量序列是相關的

穩定性:

非穩定,雖然插入排序是穩定的,但是希爾排序在插入的時候是跳躍性插入的,有可能破壞穩定性。

/**
	 * 希爾排序
	 * 適用於數據量中等的情況下,幾萬到幾十萬
	 * 麻將在玩的過程中的重複排序
	 */
	public static void shellSort(int[] arr, int step){
		for(int k = 0; k < step; k++){
			for(int i = k+step; i < arr.length; i += step){
				int j = i;
				int target = arr[j];
				while(j > step-1 && target < arr[j-step]){
					arr[j] = arr[j-step];
					j -= step;
				}
				arr[j] = target;
			}
		}
	}
 法二:更好理解
 public static void shellSort(int[] arr){
		int N = arr.length;
//		先進行分組,最開始時的增量(gap)爲數組長度的一半
		for(int gap = N/2; gap > 0; gap /= 2){
//			對各個分組進行插入排序
			for(int i = gap; i < N; i++){
//				將arr[i]插入到所在分組的正確位置
				insertI(arr, gap, i);
			}
		}
	}
	public static void insertI(int[] arr, int gap, int i){
		int inserted = arr[i];
		int j;
//		插入的時候按組進行插入(組內元素兩兩相隔gap)
		for(j = i-gap; j >= 0 && inserted < arr[j]; j -= gap){
			arr[j+gap] = arr[j];
		}
		arr[j+gap] = inserted;
	}

8. 堆排序

堆排序是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。堆分爲大根堆和小根堆,是完全二叉樹。

知識點:

完全二叉樹:除了最後一層之外的其他每一層都被完全填充,並且所有節點都保持左對齊。

滿二叉樹:除了葉子節點之外的每一個節點都有兩個孩子,每一層(包含最後一層)都被完全填充。

完滿二叉樹:除了葉子節點之外的每一個節點都有兩個孩子節點。

/**
	 * 堆排序
	 * 利用堆這種數據結構所設計的一種排序算法,
	 * 堆是一個近似完全的二叉樹結構,並同時滿足堆的性質
	 * (節點的鍵值或索引總是小於(或者大於)它的父節點
	 * 排序過程:
	 * 1. 從最後一個非葉子節點開始,每三個節點做一次大小比較,最小的做根
	 * 2. 取走整個樹的根節點,把最後一個葉子作爲根節點
	 * 3. 重複1和2,直到所有節點被取走
	 * 使用情況:
	 * 在大數據情況下找到錢n個數據
	 */
	public static void heapSort(int[] arr, int len){
//		從最後一個非結點開始建堆
		for(int i = len/2 -1; i >= 0; i--){
			maxHeapify(arr, i, len-1);
		}
//		排序,根節點和最後一個葉子節點交換位置
//		換完後,取走根,然後調整堆
		for(int i = len-1; i > 0; i--){
			int temp = arr[0];
			arr[0] = arr[i];
			arr[i] = temp;
			maxHeapify(arr, 0, i-1);
		}
	}
//	建立最大堆
	public static void maxHeapify(int[] arr, int start, int end){
		int dad = start;
		int son = dad*2 + 1;
		while(son <= end){
//			棧出兩個子節點中大的雨父節點比較
			if(son+1 <= end && arr[son] < arr[son+1]){
				son++;
			}
			if(arr[dad] > arr[son]){
				return;
			}else{
				int temp = arr[dad];
				arr[dad] = arr[son];
				arr[son] = temp;
//				遞歸下一層
				dad = son;
				son = dad*2 + 1;
			}
		}
	}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章