【算法編程】排序算法詳解

一、排序算法複雜度比較

T(N) = a*T(N/b) + O(N^d)

  • log(b,a)>dlog(b,a) > d -> 複雜度爲O(Nlog(b,a))O(N^{log(b,a)})
  • log(b,a)=dlog(b,a) = d -> 複雜度爲O(NdlogN)O(N^d * logN)
  • log(b,a)<dlog(b,a) < d -> 複雜度爲O(Nd)O(N^d)

二、冒泡排序

  • 時間複雜度 O(N2)O(N^2),額外空間複雜度 O(1)O(1)
  • 可實現成穩定的
    在這裏插入圖片描述

經過上圖一輪後,最大的數就到了最後位置,接下來我們對剩下的1,2,3,6,4進行同樣的方式,最終可以將數值從小到大排序

Java代碼:

package day01;

/**
 * 冒泡排序
 * @author Danycym
 *
 */
public class Code01_BubbleSort {
	public static void bubbleSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int i = arr.length - 1; i > 0; i--) {
			for (int j = 0; j < i; j++) {
				if (arr[j] > arr[j + 1]) {
					swap(arr, j, j + 1);
				}
			}
		}
	}
	
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
		printArray(arr);
		bubbleSort(arr);
		printArray(arr);
	}
}

動圖參考來自網絡:
在這裏插入圖片描述

三、插入排序

  • 時間複雜度 O(N2)O(N^2),額外空間複雜度 O(1)O(1)
  • 可實現成穩定的

在這裏插入圖片描述

插入排序和我們平時插撲克牌類似,上圖中橙色框內表示手裏的牌,橙色框下一個元素表示下一張要抓的牌,每抓到一張新的牌,我們就將其插入到手裏對應的位置(這裏需要從新牌的上一張到第一張逐一比較,比手裏的牌小就交換,直到比手裏的牌大,此時插入到小牌後面,大牌前面完成插入),依次類推,直到最後一張牌

Java代碼:

package day01;

/**
 * 插入排序
 * @author Danycym
 *
 */
public class Code02_InsertionSort {
	public static void insertionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for(int i = 1; i < arr.length; i++) {
			for(int j = i - 1; j >= 0; j--) {
				if(arr[j] > arr[j+1]) {
					swap(arr, j, j + 1);
				}
			}
		}
	}
	
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
		printArray(arr);
		insertionSort(arr);
		printArray(arr);
	}

}

動圖參考來自網絡:

在這裏插入圖片描述

四、選擇排序

  • 時間複雜度 O(N2)O(N^2),額外空間複雜度 O(1)O(1)
  • 不可實現成穩定的
    在這裏插入圖片描述

每次設定橙色框位置爲最小值,如果該最小值後面的數有比它小的就和它交換

Java代碼:

package day01;

/**
 * 選擇排序
 * @author Danycym
 *
 */
public class Code03_SelectionSort {
	public static void selectionSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		
		for(int i = 0; i < arr.length - 1; i++) {
			int minIndex = i;
			for(int j = i + 1; j < arr.length; j++) {
				minIndex = arr[j] < arr[minIndex] ? j : minIndex;
			}
			swap(arr, i, minIndex);
		}
	}
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
		printArray(arr);
		selectionSort(arr);
		printArray(arr);
	}
}

動圖參考來自網絡:

在這裏插入圖片描述

五、歸併排序

  • 時間複雜度 O(NlogN)O(N*logN),額外空間複雜度 O(N)O(N)
  • 可實現成穩定的
    在這裏插入圖片描述

將原問題拆分成子問題,再對子問題進行同樣的操作,直到不能再分,然後就是排序合併

Java代碼:

package day01;

/**
 * 歸併排序
 * @author Danycym
 *
 */
public class Code04_MergeSort {
	public static void mergeSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		mergeSort(arr, 0, arr.length - 1);
	}
	private static void mergeSort(int[] arr, int l, int r) {
		if(l == r) {
			return;
		}
		int mid = l + ((r - l) >> 1);  //中點位置
		mergeSort(arr, l, mid);  //左分支
		mergeSort(arr, mid + 1, r);  //右分支
		merge(arr, l, mid, r);  //合併
	}
	private static void merge(int[] arr, int l, int m, int r) {
		int[] help = new int[r - l + 1];  //存儲合併結果
		int i = 0;
		int p1 = l;
		int p2 = m + 1;
		//比較大小,依次添加到help中
		while(p1 <= m && p2 <= r) {
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		//p1還沒到最後,直接將後面的都添加到help
		while(p1 <= m) {
			help[i++] = arr[p1++];
		}
		//p2還沒到最後,直接將後面的都添加到help
		while(p2 <= r) {
			help[i++] = arr[p2++];
		}
		//最後將help中的元素都拷貝到原數組中
		for(i = 0; i < help.length; i++) {
			arr[l + i] = help[i];
		}
	}
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
		printArray(arr);
		mergeSort(arr);
		printArray(arr);
	}
}

動圖參考來自網絡:

在這裏插入圖片描述

六、快速排序

  • 時間複雜度 O(NlogN)O(N*logN),額外空間複雜度 O(logN)O(logN)
  • 不可實現成穩定的

在這裏插入圖片描述

上圖中,我們選擇最後一個元素作爲劃分的那個值,程序中做了修改,每次隨機選擇一個數作爲劃分的數,具體做法如下:

  • 先隨機選擇一個元素,然後將它與最後一個元素交換,最後在進行看劃分
  • swap(arr, l + (int)(Math.random() * (r - l + 1)), r);

Java代碼:

package day01;
/**
 * 快速排序
 * @author Danycym
 *
 */
public class Code05_QuickSort {

	public static void quickSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		quickSort(arr, 0, arr.length - 1);
	}
	
	public static void quickSort(int[] arr, int l, int r) {
		if(l < r) {
			//隨機選擇一個元素作爲劃分的數
			swap(arr, l + (int)(Math.random() * (r - l + 1)), r);
			int[] p = partition(arr, l, r);
			quickSort(arr, l, p[0] - 1);
			quickSort(arr, p[1] + 1, r);
		}
	}
	//交換
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	//劃分
	public static int[] partition(int[] arr, int l, int r) {
		int less = l - 1;
		int more = r;
		while(l < more) {
			if(arr[l] < arr[r]) {//遍歷到的元素比最後一個元素小
				swap(arr, ++less, l++);//左邊界往右擴大1,並與當前遍歷到的元素交換
			}else if(arr[l] > arr[r]) {//遍歷到的元素比最後一個元素大
				swap(arr, --more, l);//右邊界往左擴大1,並與當前遍歷到的元素交換
			}else {//否則,直接遍歷下個元素
				l++;
			}
		}
		swap(arr, more, r);//交換arr[more]與arr[r]
		return new int[] {less + 1, more};
	}
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5};
		printArray(arr);
		quickSort(arr);
		printArray(arr);
	}

}

動圖參考來自網絡:

在這裏插入圖片描述

黃色代表基數(劃分的數)
紅色代表當前遍歷的
綠色代表小於基數的
紫色代表大於基數的
橙色代表排序好的

  • 該圖每次都是以第一個元素作爲劃分基數的

七、堆排序

  • 時間複雜度 O(NlogN)O(N*logN),額外空間複雜度 O(1)O(1)
  • 不可實現成穩定的

數組理解爲完全二叉樹:
在這裏插入圖片描述

大根堆:

  • 堆就是完全二叉樹
  • 大根堆:在這棵完全二叉樹中,任何一棵子樹的最大值都是該子樹的頭部

在這裏插入圖片描述

將數組腦補的完全二叉樹轉換爲大根堆:

在這裏插入圖片描述

堆排序:
在這裏插入圖片描述

Java代碼:

package day01;
/**
 * 堆排序
 * @author Danycym
 *
 */
public class Code06_HeapSort {
	public static void heapSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		//構建大根堆
		for(int i = 0; i < arr.length; i++) {
			heapInsert(arr, i);
		}
		//大根堆長度
		int heapSize = arr.length;
		//將大根堆第一個元素與此時剩下的大根堆的最後一個元素交換
		swap(arr, 0, --heapSize);
		while(heapSize > 0) {
			heapify(arr, 0, heapSize);  //進行下沉操作
			swap(arr, 0, --heapSize);
		}
	}
	
	public static void heapInsert(int[] arr, int i) {
		while(arr[i] > arr[(i -1) / 2]) {  //如果i對應的值大於其父節點
			swap(arr, i, (i -1) / 2);  //則和父節點交換
			i = (i -1) / 2;  //並將父節點位置更新爲當前遍歷
		}
	}
	//
	public static void heapify(int[] arr, int i, int heapSize) {
		int left = i * 2 + 1; //左孩子下標
		while(left < heapSize) {
			//判斷左右孩子中較大的孩子的下標
			int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
			//再將較大者和當前遍歷比較,記錄較大者下標
			largest = arr[largest] > arr[i] ? largest : i;
			if(largest == i) {  //如果相等,較大者是自己,退出
				break;
			}
			//否則交換
			swap(arr, largest, i);
			i = largest;  //更新i爲交換後的位置
			left = i * 2 + 1;
		}
	}
	//交換
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

	//打印
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5};
		printArray(arr);
		heapSort(arr);
		printArray(arr);
	}

}

動圖參考來自網絡:

在這裏插入圖片描述

八、桶排序

  • 時間複雜度 O(N)O(N),額外空間複雜度 O(N)O(N)
  • 可以實現成穩定的

桶排序是一種非基於比較的排序,其實現包括計數排序和基數排序兩種,其基本思想如下:

  1. 得到無序數組的取值範圍
  2. 根據取值範圍創建對應數量的桶
  3. 遍歷數組,將每個元素放到對應的桶中
  4. 按照順序遍歷桶中的每個元素,依次放到數組中,即可完成數組的排序

在這裏插入圖片描述

堆排序是否穩定取決於“桶”用的是什麼數據結構,如果是隊列,那麼可以保證相同的元素取出後的相對位置與“放進去”之前是相同的,如果用棧來實現桶,就不是穩定的
“桶”是一種容器,這個容器可以用多種數據結構實現,包括數組、隊列或者棧

1、計數排序

  • 桶排序的實現之計數排序

在這裏插入圖片描述

Java代碼:

package day01;
/**
 * 桶排序
 * @author Danycym
 *
 */
public class Code07_BucketSort {
	public static void bucketSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		int max = Integer.MIN_VALUE;
		//求數組的最大值
		for(int i = 0; i < arr.length; i++) {
			max = Math.max(max, arr[i]);
		}
		int[] bucket = new int[max + 1];
		for(int i = 0; i < arr.length; i++) {
			bucket[arr[i]]++;
		}
		int i = 0;
		for(int j = 0; j < bucket.length; j++) {
			while(bucket[j]-- > 0) {
				arr[i++] = j;
			}
		}
	}
	
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		int[] arr = {2, 1, 12, 1, 3, 3, 4};
		printArray(arr);
		bucketSort(arr);
		printArray(arr);
	}
}

2、基數排序

待更新。。。

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