本系列文章为慕课网相关课程笔记整理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); } }
核心在于三个区间的边界肯定,结合上述两幅图示好好理解。
思考题: