本系列文章爲慕課網相關課程筆記整理java
歸併排序(Merge)是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列。git
歸併排序是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。 將已有序的子序列合併,獲得徹底有序的序列;即先使每一個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。歸併排序算法穩定,數組須要O(n)的額外空間,鏈表須要O(log(n))的額外空間,時間複雜度爲O(nlog(n)),算法不是自適應的,不須要對數據的隨機讀取。github
實現原理:算法
public class MergeSort { public static void sort(int[] arr) { if (arr == null || arr.length == 0) { return ; } int n = arr.length; mergeSort(arr, 0, n - 1); } public static void mergeSort(int[] arr, int low, int high) { if (low >= high) { return ; } int mid = low + (high - low) / 2; mergeSort(arr, low, mid); mergeSort(arr, mid + 1, high); merge(arr, low, mid, high); } public static void merge(int[] arr, int low, int mid, int high) { if (arr == null || arr.length == 0) { return ; } int n = arr.length; int[] aux = Arrays.copyOfRange(arr, 0, n + 1); int i = low, j = mid + 1; for (int k = low; k <= high; k++) { if (i > mid) { //[low, mid]已經處理完了 arr[k] = aux[j - low]; j++; } else if (j > high) { //[mid + 1, high]已經處理完了 arr[k] = aux[i - low]; } else if (aux[i - low] < aux[j - low]) { arr[k] = arr[i - low]; i++; } else { arr[k] = arr[j - low]; j++; } } } }
性能分析:
在近乎有序的數組排序時,效率較低數組
方向 :dom
public static void mergeSort(int[] arr, int low, int high) { //優化點 if (high - low <= 15) { insertionSort(arr, low, high); return ; } int mid = low + (high - low) / 2; mergeSort(arr, low, mid); mergeSort(arr, mid + 1, high); //優化點 if (arr[mid] > arr[mid + 1]) { merge(arr, low, mid, high); } }
public class MergeSortBU { public static void sort(int[] arr) { if (arr == null || arr.length == 0) { return ; } int n = arr.length; for (int sz = 1; sz <= n; sz += sz) { for (int i = 0; i + sz < n; i += sz + sz) { //對arr[i...i+sz-1] 和 arr[i+sz...i+sz+sz-1]進行歸併 //注意數組越界問題 //1. 保證[i+sz,i+sz+sz-1]存在,則,i+sz < n //2. 保證i+sz+sz-1不越界 //System.out.println("排序中:sz = " + sz +", i = " + i); //System.out.println("排序區間爲: [" + i + ", " + (i + sz - 1) + // "], 以及[" + (i + sz) + ", " + (i + sz + sz -1) + "]."); if (arr[i + sz - 1] > arr[i + sz]) { merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, n - 1)); } } } } public static void merge(int[] arr, int low, int mid,int high) { if (arr == null || arr.length == 0) { return; } int n = arr.length; int[] aux = Arrays.copyOfRange(arr, 0, n - 1); int i = low, j = mid + 1; for (int k = low; k <= high; k++) { if (i > mid) { arr[k] = aux[j - low]; j++; } else if (j > high) { arr[k] = aux[i - low]; i++; } else if (arr[i] < arr[j]) { arr[k] = aux[i - low]; } else { arr[k] = aux[j - low]; } } } }
問題解答:ide
快速排序是C.R.A.Hoare於1962年提出的一種劃分交換排序。它採用了一種分治的策略,一般稱其爲分治法(Divide-and-ConquerMethod)。性能
該方法的基本思想是:測試
public static void sort(int[] arr) { if (arr == null || arr.length == 0) { return; } int n = arr.length; quickSort(arr, 0, n - 1); } public static void quickSort(int[] arr, int low, int high) { if (low >= high) { return; } int p = partition(arr, low, high); quickSort(arr, low, p - 1); quickSort(arr, p + 1, high); } public static int partition(int[] arr, int low, int high) { int v = arr[low]; //arr[low + 1....j] < v ; arr[j + 1...i] > v int j = low; for (int i = low + 1; i < high; i++) { if (arr[i] < v) { swap(arr, j + 1, i); j++; } } swap(arr, low, j); return j; } //最後一次展現swap方法 private static void swap(int[] arr, int i, int j) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; }
測試時,若是數據量較大,會報java.lang.StackOverflowError,由於遞歸太多,棧內存不夠用了。優化
方向:
在近乎有序的排序中,快排的遞歸樹的平衡度比歸併的遞歸樹的平衡度要差不少,且樹的深度可能不是logn,最壞狀況是待排序數組徹底有序,此時快排的時間複雜度退化爲O(n^2),所以在partition過程當中,選擇pivot值時的策略爲隨機選擇是很好的解決方案。
public static void quickSort(int[] arr, int low, int high) { if (high - low < 16) { insertSort(arr, low, high); } int p = partition(arr, low, high); quickSort(arr, low, p - 1); quickSort(arr, p + 1, high); } public static int partition(int[] arr, int low, int high) { long seed = System.nanoTime(); Random random = new Random(seed); int randomPos = random.nextInt(high - low + 1) + low; swap(arr, low, randomPos); int v = arr[low]; int j = low; for (int i = low + 1; i < high; i++) { if (arr[i] < v) { swap(arr, j + 1, i); j++; } } swap(arr, low, j); return j; }
public static int partition(int[] arr, int low, int high) { long seed = System.nanoTime(); Random random = new Random(seed); int randomPos = random.nextInt(high - low + 1) + low; swap(arr, low, randomPos); int v = arr[low]; int i = low + 1, j = high; while (true) { while (i < high && arr[i] < v) { i++; } while (j > low && arr[j] > v) { j--; } if (i > j) { break; } swap(arr, i, j); i++; j--; } return j; }
我寫得最6的快排
public static int partition(int[] arr, int low, int high) { long seed = System.nanoTime(); Random random = new Random(seed); int randomPos = random.nextInt(high - low + 1) + low; swap(arr, low, randomPos); int v = arr[low]; while (low < high) { while (low < high && arr[high] >= v) { high--; } arr[low] = arr[high]; while (low < high && arr[high] <= v) { low++; } arr[high] = arr[low]; } arr[low] = v; return low; }
若是存在大量相同元素,三路快排性能更佳!
三路快排中途發展圖示
三路快排結束時圖示
import java.util.Random; public class QuickSort3Ways { public static void sort(int[] arr) { if (arr == null || arr.length == 0) { return; } int n = arr.length; quickSort3Ways(arr, 0, n - 1); } public static void quickSort3Ways(int[] arr, int low, int high) { if (low >= high) { return; } long seed = System.nanoTime(); Random random = new Random(seed); int pos = random.nextInt(high - low + 1) + low; swap(arr, low, pos); int v = arr[low]; int lt = low; //arr[low+1...lt] < v 初始區間爲空 int gt = high + 1;// arr[gt...high] > v 初始區間爲空 int i = low + 1; // arr[lt+1...i] = v 初始區間爲空 while (i < gt) { if (arr[i] > v) { swap(arr, i, gt - 1); gt--; } else if (arr[i] < v) { swap(arr, lt + 1, i); lt++; i++; } else { i++; } } swap(arr, lt, low); quickSort3Ways(arr, low, lt - 1); quickSort3Ways(arr, gt, high); } }
核心在於三個區間的邊界肯定,結合上述兩幅圖示好好理解。
思考題: