一、基本思想
选择一个基准数,通过一趟排序将待排序数据分成两个独立的部分,其中一部分的所有数都比基准数小,另一部分都比基准数大,然后再使用此方法递归对两部分数据进行快排,最终实现整个数据的有序。
二、详解排序过程
现有待排序数据:
{-21, 312, 44, 11, -23, 2, 10};
蓝色为左哨兵,记为left,黄色为右哨兵记为right,红色为基数
基数=取第一个位置的元素
第一轮排序:
↓ ↓
-21, 312, 44, 11, -23, 2, 10
左哨兵找到第一个比基数-21大的数停止,即在312停止,此时left=1
右哨兵找到第一个比基数小的数停止,即在-23停止,此时right=4
此时两个哨兵尚未相遇,交换 312 和 -23
交换后:
↓ ↓
-21, -23, 44, 11, 312, 2, 10
左哨兵继续移动,到比-21大的44时停止,此时left=2
右哨兵继续移动,到比-21小的-23处停止,此时right=1
但因为left>right,表示二者已经相遇,终止本次循环,将pivot与right位置元素交换
三、代码实现:
/**
* 快速排序
* @author
* @date 2019/10/2917:06
*/
public class QuickSort {
public static void main(String[] args) {
int[] arr={-21,312,44,11,-23,2,10};
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static <T extends Comparable<? super T>> void quickSort(T[] arr) {
if (arr.length==0||arr.length==1){
return;
}
quickSort(arr, 0, arr.length - 1);
}
private static <T extends Comparable<? super T>> void quickSort(T[] arr, int left, int right) {
if (right<=left){
return;
}
//此时j左边的元素已全部小于arr[j],j右边的元素全部小于arr[j]
int j=partition(arr,left,right);
quickSort(arr,left,j-1);
quickSort(arr,j+1,right);
}
/**
* 快速排序的切分
* @param arr
* @param left
* @param right
* @param <T>
* @return
*/
public static <T extends Comparable<? super T>> int partition(T[] arr, int left, int right) {
int i=left,j=right+1;
T pivot = arr[left];
while (true){
//循环直到找到一个比pivot大的值,或到达尾部
while (arr[++i].compareTo(pivot)<0){
if (i==right){
break;
}
}
//循环直到找到一个比pivot小的值,或到达首部
while (pivot.compareTo(arr[--j])<0){
if (j==left){
break;
}
}
//如果i和j相遇,则终止循环
if (i>=j){
break;
}
//交换i和j的元素
swapReferences(arr,i,j);
}
//将pivot换到j的位置
swapReferences(arr,left,j);
return j;
}
public static <T> void swapReferences(T[] arr,int index1,int index2){
T temp=arr[index1];
arr[index1]=arr[index2];
arr[index2]=temp;
}
}
结果:
[-23, -21, 2, 10, 11, 44, 312]
四、测试10W条数据耗时
int[] arr =new int[100000];
for (int i=0;i<100000;i++){
arr[i]=(int)(Math.random()*100000);
}
long begintime=System.currentTimeMillis();
System.out.println("开始时间"+begintime);
quickSort(arr,0,arr.length-1);
long endtime=System.currentTimeMillis();
System.out.println("结束时间"+endtime);
System.out.println("用时:"+(endtime-begintime)+"ms");
结果:
开始时间1572344644967
结束时间1572344645031
用时:64ms
此时与希尔排序看不出很大的差距,将数据增加到80W条测试结果:
希尔:
开始时间1572344777821
结束时间1572344778134
用时:313ms
快速:
开始时间1572344758536
结束时间1572344758711
用时:175ms
经过多次测试,偶尔会有希尔排序比快速快的情况,但是多数情况下还是快速比希尔快
五、优化改进---三取样切分:
在有大量重复元素的情况下,假如一个元素的子数组全部重复时,我们的快速排序还会继续将其切分为更小的数组,如果我们不对它进行排序,那么性能会提升很多,从当前的线性对数级别提高到线性级别。
此时我们可以将数组切分为三部分,分别对应小于、等于和大于切分元素的数组。
思路如下:
维护一个指针lt,使得a[left]到a[lt-1]都小于切分元素v
一个指针gt,使得a[gt+1]到a[right]都大于切分元素v
一个指针i,使用a[lt]到a[i-1]都等于切分元素v
而a[i]到a[gt]元素尚未确定。
当i<=gt时循环:
1.a[i] < v: 将a[i]与a[lt]交换,并让 i 和 lt 加一,此时lt左边的一定小于v
2.a [i] > v: 将a[i]与a[gt]交换,并让 gt 减一,此时gt右边的一定大于v
3. a[i] = v : 直接让 i 加以 此时 lt 到 i-1 都等于v
实现如下:
/**
* 三取样切分
* @param arr
* @param left
* @param right
* @param <T>
*/
public static <T extends Comparable<? super T>> void quickSort3Way(T[] arr, int left, int right) {
if (right<=left){
return;
}
if (left+1<=right){
//初始化三个指针
int lt=left,i=lt+1,gt=right;
T v = arr[left];
while (i<=gt){
int result=arr[i].compareTo(v);
if (result<0){
swapReferences(arr,lt++,i++);
}else if (result>0){
swapReferences(arr,i,gt--);
}else {
i++ ;
}
}
quickSort3Way(arr,left,lt-1);
quickSort3Way(arr,gt+1,right);
}else {
insertSort(arr,left,right);
}
}
但是该算法在重复元素不多的普通情况下比标准的算法多用了很多次交换。而解决该问题的快速三向排序的最优解法由J.Bently和D.McIlroy给出答案。
算法第四版中2.3节课后练习2.3.22题给出了相关解读。
有时间再解读,相关实现如下:
package com.datastructures2.sort;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
public class Quick3Way {
// cutoff to insertion sort, must be >= 1
private static final int INSERTION_SORT_CUTOFF = 8;
// cutoff to median-of-3 partitioning
private static final int MEDIAN_OF_3_CUTOFF = 40;
// This class should not be instantiated.
private Quick3Way() {
}
/**
* Rearranges the array in ascending order, using the natural order.
*
* @param a
* the array to be sorted
*/
public static void sort(Comparable[] a) {
sort(a, 0, a.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi) {
int length = hi - lo + 1;
// cutoff to insertion sort
if (length <= INSERTION_SORT_CUTOFF) {
insertionSort(a, lo, hi);
return;
}
// use median-of-3 as partitioning element
else if (length <= MEDIAN_OF_3_CUTOFF) {
int m = median3(a, lo, lo + length / 2, hi);
exch(a, m, lo);
}
// use Tukey ninther as partitioning element
else {
int eps = length / 8;
int mid = lo + length / 2;
int m1 = median3(a, lo, lo + eps, lo + eps + eps);
int m2 = median3(a, mid - eps, mid, mid + eps);
int m3 = median3(a, hi - eps - eps, hi - eps, hi);
int ninther = median3(a, m1, m2, m3);
exch(a, ninther, lo);
}
// Bentley-McIlroy 3-way partitioning
int i = lo, j = hi + 1;
int p = lo, q = hi + 1;
Comparable v = a[lo];
while (true) {
while (less(a[++i], v))
if (i == hi)
break;
while (less(v, a[--j]))
if (j == lo)
break;
// pointers cross
if (i == j && eq(a[i], v))
exch(a, ++p, i);
if (i >= j)
break;
exch(a, i, j);
if (eq(a[i], v))
exch(a, ++p, i);
if (eq(a[j], v))
exch(a, --q, j);
}
i = j + 1;
for (int k = lo; k <= p; k++)
exch(a, k, j--);
for (int k = hi; k >= q; k--)
exch(a, k, i++);
sort(a, lo, j);
sort(a, i, hi);
}
private static void insertionSort(Comparable[] a, int lo, int hi) {
for (int i = lo; i <= hi; i++)
for (int j = i; j > lo && less(a[j], a[j - 1]); j--)
exch(a, j, j - 1);
}
// 返回a [left],a [mid]和a [right]之间的中值元素的索引
private static int median3(Comparable[] a, int left, int mid, int right) {
return (less(a[left], a[mid]) ? (less(a[mid], a[right]) ? mid : less(a[left], a[right]) ? right : left)
: (less(a[right], a[mid]) ? mid : less(a[right], a[left]) ? right : left));
}
// is v < w ?
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
// does v == w ?
private static boolean eq(Comparable v, Comparable w) {
return v.compareTo(w) == 0;
}
// exchange a[i] and a[j]
private static void exch(Object[] a, int i, int j) {
Object swap = a[i];
a[i] = a[j];
a[j] = swap;
}
/***************************************************************************
* Check if array is sorted - useful for debugging.
***************************************************************************/
private static boolean isSorted(Comparable[] a) {
for (int i = 1; i < a.length; i++)
if (less(a[i], a[i - 1]))
return false;
return true;
}
// print array to standard output
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
StdOut.println(a[i]);
}
}
/**
* Reads in a sequence of strings from standard input; quicksorts them
* (using an optimized version of quicksort); and prints them to standard
* output in ascending order.
*
* @param args
* the command-line arguments
*/
public static void main(String[] args) {
String[] a = StdIn.readAllStrings();
Quick3Way.sort(a);
assert isSorted(a);
show(a);
}
}