三路快速排序算法——上接双路快速排序算法
有没有相比于双路快速排序算法更适合处理大规模重复数据的优化呢?答案自然是有的,也就是三路快速排序算法。
三路快速排序的思想非常简单,我们之前都把数组分成两部分,小于v和大于v,而在三路快速排序中,我们将数组分成三部分,小于v、等于v和大于v,这样分割之后,我们再次递归就可以不考虑等于v的部分,而是直接在大于v和小于v两部分进行递归,当v的数量很大时,这样所带来的的效率提升非常明显。
下面详细说明一下排序的逻辑:
- 我们设置lt索引来指向小于v的最后一个元素所在的位置,同样的,gt指向第一个大于v的元素,i表示我们正要处理的元素
- 当i处元素等于v时,不需要做处理,i指向下一个元素即可
- 当i处元素小于v时,我们将i处元素和lt+1处元素进行交换,然后维护lt向后移动一位,然后i指向下一个元素
- 当i处元素大于v时,我们将i处元素和gt-1处元素进行交换,然后维护gt向前移动一位,但是此时要注意,交换过来的i处元素并没有被处理,所以i不需要移动而是直接再次处理交换过来的元素
- 当i和gt重合时,操作完成
代码实现
import java.util.*;
public class QuickSort3Ways {
// 我们的算法类不允许产生任何实例
private QuickSort3Ways(){}
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r){
// 对于小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l, (int)(Math.random()*(r-l+1)) + l );
Comparable v = arr[l];
int lt = l; // arr[l+1...lt] < v
int gt = r + 1; // arr[gt...r] > v
int i = l+1; // arr[lt+1...i) == v
while( i < gt ){
if( arr[i].compareTo(v) < 0 ){
swap( arr, i, lt+1);
i ++;
lt ++;
}
else if( arr[i].compareTo(v) > 0 ){
swap( arr, i, gt-1);
gt --;
}
else{ // arr[i] == v
i ++;
}
}
swap( arr, l, lt );
sort(arr, l, lt-1);
sort(arr, gt, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
sort(arr, 0, n-1);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
测试
我们分别针对乱序数组,近乎有序数组和含大量重复元素数组进行测试
测试代码
public static void main(String[] args) {
//测试乱序数组
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 1,1000000);
Integer[] arr1 = Arrays.copyOf(arr,arr.length);
Integer[] arr2 = Arrays.copyOf(arr,arr.length);
SortTestHelper.testSort("com.company.MergeSort2", arr);
System.out.println();
SortTestHelper.testSort("com.company.QuickSort2Ways",arr1);
System.out.println();
SortTestHelper.testSort("com.company.QuickSort3Ways",arr2);
return;
}
测试结果
可以看出,三路快速排序的效率不是很高,但是也在一个量级上,可以接受
测试代码
public static void main(String[] args) {
//测试含大量重复元素的数据
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 1,10);
Integer[] arr1 = Arrays.copyOf(arr,arr.length);
Integer[] arr2 = Arrays.copyOf(arr,arr.length);
SortTestHelper.testSort("com.company.MergeSort2", arr);
System.out.println();
SortTestHelper.testSort("com.company.QuickSort2Ways",arr1);
System.out.println();
SortTestHelper.testSort("com.company.QuickSort3Ways",arr2);
return;
}
测试结果
可以看出,在处理含有大量重复元素的数据时,三路快速排序的效率要远远超出其他两种
测试代码
public static void main(String[] args) {
//测试基本有序数据
int N = 1000000;
Integer[] arr = SortTestHelper.generateNearlyOrderedArray(N, 10);
Integer[] arr1 = Arrays.copyOf(arr,arr.length);
Integer[] arr2 = Arrays.copyOf(arr,arr.length);
SortTestHelper.testSort("com.company.MergeSort2", arr);
System.out.println();
SortTestHelper.testSort("com.company.QuickSort2Ways",arr1);
System.out.println();
SortTestHelper.testSort("com.company.QuickSort3Ways",arr2);
return;
}
测试结果
在处理基本有序的数据时,三种排序方法差距不大
总结
我们根据具体情况去选择适合的算法,一般来说我们可以选择三路快速排序,它在处理重复键值的优势以及在其他情况下可以接受的效率都是很不错的。