思维导图链接
参考左程云算法课程
4.归并排序和几个经典题目分析 总览
题1:颜色分类
颜色分类题目链接
题目描述:
-
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,
使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
代码实现
package class05;
public class Code01_SortColors {
public void sortColors(int[] nums) {
// 1.先定义好指针,划分好三个区间
int pL = -1; // 左区间指针,[0,pL]<1
int i = 0; // 中间区指针,[pL + 1,pR - 1]=1
int pR = nums.length; // 右区间指针[pR,n-1]>1
// 2.i依次遍历,将元素放到指定区间内
while(i < pR) {
if(nums[i] < 1) { // 化到左区间
swap(nums, i ++, ++pL); // 左区间先扩容,再交换,i遍历下个元素
} else if(nums[i] > 1) { // 化到右区间
swap(nums, i, --pR); // 右区间先扩容,再交换
} else { // 注意此时i放的是从右边换的元素,没有判断过,要不动,再判断一次
i ++; // 相等,就放到中间区域
}
}
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
题2:实现快排
单路快排
// 1. 单路排序法,划分两个区间
public static <E extends Comparable<E>> void sort1Way(E[] arr) {
if(arr == null || arr.length < 2) return;
Random rad = new Random();
sort1Way(arr, 0, arr.length - 1, rad);
}
private static <E extends Comparable<E>>void sort1Way(E[] arr, int left, int right, Random rad) {
if(left >= right) return; // 注意不能是left==right,有可能直接L>R
// 划分好左右区间,返回分界点指针位置
int pM = partition(arr, left, right, rad);
// 继续左右划分,确定大小的相等位置,区间最终化完了,也排好序了
sort1Way(arr, left, pM -1, rad);
sort1Way(arr, pM + 1, right, rad);
}
private static <E extends Comparable<E>>int partition(E[] arr, int left, int right, Random rad) {
// 1. 先随机确定要划分的元素
int radIndex = left + rad.nextInt(right - left + 1);
swap(arr, left, radIndex);
E v = arr[left];
// 2.定义好指针,划分好左右区间
// [left + 1, pL]<v,[pL + 1,right]>=v,pL与left交换后,pL=v返回,left位置<V
int pL = left;
int i = left + 1;
while(i <= right) {
if(arr[i].compareTo(v) < 0) {
swap(arr, i ++, ++pL);
} else {
i ++;
}
}
// 3. 将pL与left交换,此时pL即分界点位置
swap(arr, left, pL);
return pL;
}
private static<E> void swap(E[] arr, int i, int j) {
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
双路快排
// 2. 双路排序法,划分两个区间,从两边向中间遍历
public static <E extends Comparable<E>> void sort2Way(E[] arr) {
if(arr == null || arr.length < 2) return;
Random rad = new Random();
sort2Way(arr, 0, arr.length - 1, rad);
}
private static <E extends Comparable<E>>void sort2Way(E[] arr, int left, int right, Random rad) {
if(left >= right) return;
int p = partition2(arr, left, right, rad);
sort2Way(arr, left, p - 1, rad);
sort2Way(arr, p + 1, right, rad);
}
private static <E extends Comparable<E>>int partition2(E[] arr, int left, int right, Random rad) {
// 1. 先随机确定要划分的元素
swap(arr, left, left + rad.nextInt(right - left + 1));
E v = arr[left];
// 2.定义好指针,划分好左右区间,两指针即化分区间,也要遍历所有元素
// 交换前,[left + 1, pL -1]<=v,[pR + 1,right]>=v,
// pL>pR结束,即pR跑到左区间-->pR=pL-1,pL=pR+1-->[left + 1, pR]<=v,[pL,right]>=v
int pL = left + 1; // 从左往右遍历所有要比较的元素
int pR = right; // 从右往左遍历所有要比较的元素
while(true) {
while(pL <= pR && arr[pL].compareTo(v) < 0) pL ++; // pl<v,一直推进,直到pL>=v,停下处理
while(pL <= pR && arr[pR].compareTo(v) > 0) pR --; // pR>v,一直推进,直到pR<=v,停下处理
if(pL >= pR) break; // 大循环里,=时,指的是都指向v,可以结束
if(arr[pL].compareTo(arr[pR]) != 0) { // 相等时都为v,无需交换
swap(arr, pL, pR);
}
// 处理完后继续推进。直到pL>pR结束。=不结束,不知是哪个区间,错过一个后,才能确定pR跑到了左区间
pL ++;
pR --;
}
swap(arr, left, pR);
return pR;
}
三路快排
// 3. 三路快排,划分三个区间,从左往右遍历
public static <E extends Comparable<E>> void sort3Way(E[] arr) {
Random rad = new Random();
sort3Way(arr, 0, arr.length - 1, rad);
}
private static <E extends Comparable<E>> void sort3Way(E[] arr, int left, int right, Random rad) {
if (left >= right) return;
// 1.找划分元素
swap(arr, left, left + rad.nextInt(right - left + 1));
E v = arr[left];
// 2. 化区间,左右初始均为空,是用i指针遍历
// [left, pL]<v, [pL + 1, pR - 1]=v,[pR,right]>v
int pL = left;
int i = left + 1;
int pR = right + 1;
while(i < pR) {
if(arr[i].compareTo(v) < 0) {
swap(arr, i ++, ++pL);
} else if(arr[i].compareTo(v) > 0) {
swap(arr, i, --pR);
} else {
i ++;
}
}
swap(arr, left, pL);
// 遍历完交换后,pL指向v,i=pR指向>v
// [left, pL - 1]<v, [pL, pR - 1]=v,[pR,right]>v
sort3Way(arr, left, pL - 1, rad);
sort3Way(arr, pR, right, rad);
}
代码测试
public static void main(String[] args) {
int[] nums = {100000, 1000000};
for(int n : nums) {
System.out.println("Random Array:");
Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
Integer[] arr2 = Arrays.copyOf(arr, arr.length);
Integer[] arr3 = Arrays.copyOf(arr, arr.length);
SortingHelper.sortTime("QuickSort1Way", arr);
SortingHelper.sortTime("QuickSort2Way", arr2);
SortingHelper.sortTime("QuickSort3Way", arr3);
System.out.println();
System.out.println("Order Array:");
arr = ArrayGenerator.generateOrderedArray(n);
arr2 = Arrays.copyOf(arr, arr.length);
arr3 = Arrays.copyOf(arr, arr.length);
SortingHelper.sortTime("QuickSort1Way", arr);
SortingHelper.sortTime("QuickSort2Way", arr2);
SortingHelper.sortTime("QuickSort3Way", arr3);
System.out.println();
System.out.println("Same Value Array:");
arr = ArrayGenerator.generateRandomArray(n, 1);
arr2 = Arrays.copyOf(arr, arr.length);
arr3 = Arrays.copyOf(arr, arr.length);
//SortingHelper.sortTime("QuickSort1Way", arr);
SortingHelper.sortTime("QuickSort2Way", arr2);
SortingHelper.sortTime("QuickSort3Way", arr3);
System.out.println();
}
Random Array:
QuickSort1Way, n = 100000 : 0.029789 s
QuickSort2Way, n = 100000 : 0.025469 s
QuickSort3Way, n = 100000 : 0.039285 s
Order Array:
QuickSort1Way, n = 100000 : 0.008768 s
QuickSort2Way, n = 100000 : 0.005191 s
QuickSort3Way, n = 100000 : 0.013546 s
Same Value Array:
QuickSort2Way, n = 100000 : 0.006793 s
QuickSort3Way, n = 100000 : 0.000164 s
Random Array:
QuickSort1Way, n = 1000000 : 0.257176 s
QuickSort2Way, n = 1000000 : 0.228321 s
QuickSort3Way, n = 1000000 : 0.309179 s
Order Array:
QuickSort1Way, n = 1000000 : 0.107494 s
QuickSort2Way, n = 1000000 : 0.059241 s
QuickSort3Way, n = 1000000 : 0.155168 s
Same Value Array:
QuickSort2Way, n = 1000000 : 0.072794 s
QuickSort3Way, n = 1000000 : 0.002024 s