快速排序法04:三路快速排序法
核心思想:将数组拆分成三个区间,对于等于标定点的元素,也单独划分区间来保存,在下次循环的时候不考虑这部分区间,如果是元素相同的数组,就不会重复遍历这些元素了,这称为三路快速排序法
设置三个指针lt、gt和i,lt指向左区间最后一个元素,gt指向右区间第一个元素,当arr[i] < arr[left]时和arr[++lt]互换,arr[i] > arr[left]时和arr[--gt]互换,相等时只进行i++
执行partition()方法前循环不变量:arr[left + 1, lt] < v,arr[lt + 1, i - 1] == v,arr[gt, right] > v
执行partition()方法后循环不变量:arr[left, lt - 1] < v,arr[lt, gt - 1] == v,arr[gt, right] > v
import java.util.Arrays;
import java.util.Random;
public class Algorithm {
public static void main(String[] args) {
Integer[] arr = {3, 2, 5, 1, 0, 0};
QuickSort.sort3ways(arr);
System.out.println(Arrays.toString(arr));
}
}
class QuickSort {
private QuickSort(){}
public static<E extends Comparable<E>> void sort3ways(E[] arr){
Random random = new Random();
E temp = null;
sort3ways(arr, 0, arr.length - 1, temp, random);
}
public static<E extends Comparable<E>> void sort3ways(E[] arr, int left, int right, E temp, Random random){
if (left >= right){
return;
}
int[] res = partition3ways(arr, left, right, temp, random);
sort3ways(arr, left, res[0], temp, random);
sort3ways(arr, res[1], right, temp, random);
}
/**
* 循环不变量:arr[left + 1, lt] < v,arr[lt + 1, i - 1] == v,arr[gt, right] > v
*/
public static<E extends Comparable<E>> int[] partition3ways(E[] arr, int left, int right, E temp, Random random){
int p = random.nextInt(right - left + 1) + left;
swap(arr, p, left, temp);
int i = left + 1;
int lt = left;
int gt = right + 1;
/**
* i == gt时,arr[gt]肯定大于arr[left],所以要结束循环
*/
while (i < gt){
/**
* 当arr[i] < arr[left]时,将arr[i]和arr[lt + 1]互换,i继续向前
*/
if (arr[i].compareTo(arr[left]) < 0){
lt++;
swap(arr, lt, i, temp);
i++;
}
/**
* 当arr[i] == arr[left]时,lt不动,i继续向前
*/
else if (arr[i].compareTo(arr[left]) == 0){
i++;
}
/**
* 当arr[i] > arr[left]时,将arr[i]和arr[gt - 1]互换,i不动,因为换过来的元素还要进行判断
*/
else if (arr[i].compareTo(arr[left]) > 0){
gt--;
swap(arr, gt, i, temp);
}
}
/**
* 遍历结束后,将arr[lt]和arr[left]互换,此时元素区间发生了改变
* arr[left, lt - 1] < v,arr[lt, gt - 1] == v,arr[gt, right] > v
* 其中等于arr[left]的区间不用再返回,只需返回两侧区间的索引
*/
swap(arr, lt, left, temp);
int[] res = {lt - 1, gt};
return res;
}
public static<E extends Comparable<E>> void swap(E[] arr, int i, int j, E temp){
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
复杂度分析
在元素都相同的数组中,三路快速排序法的时间复杂度为O(n)
期望复杂度为O(nlogn)
双路和三路快速排序法性能比较
import java.util.Arrays;
import java.util.Random;
public class Algorithm {
public static void main(String[] args) {
Integer[] testScale = {10000, 100000};
for (Integer n : testScale){
Integer[] randomArr = ArrayGenerator.generatorRandomArray(n, 1);
Integer[] sortedArr = ArrayGenerator.generatorSortedArray(n);
Integer[] arr1 = Arrays.copyOf(randomArr, randomArr.length);
Integer[] arr3 = Arrays.copyOf(randomArr, randomArr.length);
Integer[] arr2 = Arrays.copyOf(sortedArr, sortedArr.length);
Integer[] arr4 = Arrays.copyOf(sortedArr, sortedArr.length);
System.out.println("测试元素完全相同数组排序性能");
System.out.println();
Verify.testTime("QuickSort2Ways", arr1);
Verify.testTime("QuickSort3Ways", arr3);
System.out.println();
System.out.println("测试有序数组排序性能");
System.out.println();
Verify.testTime("QuickSort2Ways", arr2);
Verify.testTime("QuickSort3Ways", arr4);
System.out.println();
}
}
}
class QuickSort {
private QuickSort() {}
public static <E extends Comparable<E>> void sort2ways(E[] arr) {
Random random = new Random();
E temp = null;
sort2ways(arr, 0, arr.length - 1, temp, random);
}
public static <E extends Comparable<E>> void sort2ways(E[] arr, int left, int right, E temp, Random random) {
if (left >= right) {
return;
}
int p = partition2ways(arr, left, right, temp, random);
sort2ways(arr, left, p - 1, temp, random);
sort2ways(arr, p + 1, right, temp, random);
}
public static <E extends Comparable<E>> int partition2ways(E[] arr, int left, int right, E temp, Random random) {
int p = random.nextInt(right - left + 1) + left;
temp = arr[p];
arr[p] = arr[left];
arr[left] = temp;
int i = left + 1;
int j = right;
while (i <= j) {
if (arr[i].compareTo(arr[left]) < 0) {
i++;
} else if (arr[j].compareTo(arr[left]) > 0) {
j--;
} else {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
temp = arr[j];
arr[j] = arr[left];
arr[left] = temp;
return j;
}
public static<E extends Comparable<E>> void sort3ways(E[] arr){
Random random = new Random();
E temp = null;
sort3ways(arr, 0, arr.length - 1, temp, random);
}
public static<E extends Comparable<E>> void sort3ways(E[] arr, int left, int right, E temp, Random random){
if (left >= right){
return;
}
int[] res = partition3ways(arr, left, right, temp, random);
sort3ways(arr, left, res[0], temp, random);
sort3ways(arr, res[1], right, temp, random);
}
public static<E extends Comparable<E>> int[] partition3ways(E[] arr, int left, int right, E temp, Random random){
int p = random.nextInt(right - left + 1) + left;
swap(arr, p, left, temp);
int i = left + 1;
int lt = left;
int gt = right + 1;
while (i < gt){
if (arr[i].compareTo(arr[left]) < 0){
lt++;
swap(arr, lt, i, temp);
i++;
}
else if (arr[i].compareTo(arr[left]) == 0){
i++;
}
else if (arr[i].compareTo(arr[left]) > 0){
gt--;
swap(arr, gt, i, temp);
}
}
swap(arr, lt, left, temp);
int[] res = {lt - 1, gt};
return res;
}
public static<E extends Comparable<E>> void swap(E[] arr, int i, int j, E temp){
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
class ArrayGenerator {
private ArrayGenerator (){}
public static Integer[] generatorRandomArray (Integer n, Integer maxBound){
Integer[] arr = new Integer[n];
Random random = new Random();
for (int i = 0; i < n; i++) {
arr[i] = random.nextInt(maxBound);
}
return arr;
}
public static Integer[] generatorSortedArray (Integer n){
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++) {
arr[i] = i;
}
return arr;
}
}
class Verify {
private Verify (){}
public static<E extends Comparable<E>> boolean isSorted(E[] arr){
for (int i = 0; i < arr.length - 1; i++) {
if (arr[i].compareTo(arr[i + 1]) > 0) {
return false;
}
}
return true;
}
public static<E extends Comparable<E>> void testTime(String AlgorithmName, E[] arr) {
long startTime = System.nanoTime();
if (AlgorithmName.equals("QuickSort2Ways")) {
QuickSort.sort2ways(arr);
}
if (AlgorithmName.equals("QuickSort3Ways")) {
QuickSort.sort3ways(arr);
}
long endTime = System.nanoTime();
if (!Verify.isSorted(arr)){
throw new RuntimeException(AlgorithmName + "算法排序失败!");
}
System.out.println(String.format("%s算法,测试用例为%d,执行时间:%f秒", AlgorithmName, arr.length, (endTime - startTime) / 1000000000.0));
}
}