基本思想
快速排序也是基于分治算法的。步骤如下:
(1)选择一个基准元素,通常选择第一个元素或者最后一个元素;
(2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基 准元素值小。另一部分记录的元素值比基准值大;
(3)此时基准元素在其排好序后的正确位置;
(4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序
如下图所示:
Java版本实现
package com.wuyi.notecode.sort;
import java.util.Random;
public class QuickSort<T extends Comparable<T>> extends Sort<T> {
@Override
public void sort(T[] nums) {
sort(nums, 0, nums.length - 1);
}
private void sort(T[] nums, int low, int high) {
if (low >= high)
return;
int j = help(nums, low, high);
sort(nums, low, j - 1);
sort(nums, j + 1, high);
}
private int help(T[] nums, int low, int high) {
int i = low , j = high + 1;
T value = nums[low];
while (true) {
while (less(nums[++i], value) && i < high ) ;
while (less(value, nums[--j]) && j > low ) ;
if (i >= j)
break;
swap(nums, i, j);
}
swap(nums, low, j);
return j;
}
public static void main(String[] args) {
QuickSort<Integer> sortInt = new QuickSort<>();
Integer a[] = new Integer[1000];
Random r = new Random();
for (int i = 0; i < 1000; i++){
a[i] = r.nextInt(1000);
}
sortInt.sort(a);
boolean res = true;
for (int i = 1; i < 1000; i++){
if (a[i] < a[i - 1]) {
res = false;
break;
}
}
System.out.println("最终结果:" + res);
QuickSort<Double> sortDouble = new QuickSort<>();
Double b[] = {5.2, 8.3, 6.9, 6.1, 5.1, 9.63};
sortDouble.sort(b);
System.out.println("最终结果:");
sortDouble.display(b);
}
}
抽象类 Sort
package com.wuyi.notecode.sort;
public abstract class Sort<T extends Comparable<T>>{
public abstract void sort(T[] nums);
protected boolean less(T v, T w){
return v.compareTo(w) < 0;
}
protected void swap(T[] nums, int i, int j){
if (i == j)
return;
T temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
protected void display(T[] nums){
for (T a : nums){
System.out.print(a + " ");
}
System.out.println();
}
}
算法分析
在归并排序中,我们详细推算了时间复杂度,快速排序与归并排序一样采取了分治算法,它的时间复杂度也是O(N*log2N)。
对于分治算法一般都是如此,用递归的方法把数据项分为两组,然后调用自身来分别处理每一组数据。算法实际上是以2为底,运行时间与N*log2N成正比。
对于快速排序来说,最理想的状态是随机分布的数据,即我们任意选定的枢纽处于中间位置,有一半元素小于它,有一半元素大于它。当数据时由小到大排列或者由大到小排列时,快速排序的效率最低,时间复杂度扩大为O(N2)。
算法改进
1 切换到插入排序
因为快速排序在小数组中也会递归调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。
2 三数取中
最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。一种折中方法是取 3 个元素,并将大小居中的元素作为切分元素。
public class QuickSort<T extends Comparable<T>> extends Sort<T> {
@Override
public void sort(T[] nums) {
sort(nums, 0, nums.length - 1);
}
protected void sort(T[] nums, int low, int high) {
if (low >= high)
return;
int j = help(nums, low, high);
sort(nums, low, j - 1);
sort(nums, j + 1, high);
}
private int help(T[] nums, int low, int high) {
int i = low , j = high + 1;
// T value = nums[low];
T value = pivot(nums, low, high);
while (true) {
while (less(nums[++i], value) && i < high ) ;
while (less(value, nums[--j]) && j > low ) ;
if (i >= j)
break;
swap(nums, i, j);
}
swap(nums, low, j);
return j;
}
private T pivot(T[] nums, int left , int right) {
int mid = ( left + right ) / 2;
if(nums[mid].compareTo(nums[right]) > 0)
swap(nums, mid , right);
if(nums[left].compareTo(nums[right]) > 0)
swap(nums, left , right);
if(nums[mid].compareTo(nums[left]) > 0)
swap(nums, mid, left);
return nums[left];
}
}
3 三向切分
对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。
三向切分快速排序对于有大量重复元素的随机数组可以在线性时间内完成排序。
public class ThreeWayQuickSort<T extends Comparable<T>> extends QuickSort<T> {
@Override
protected void sort(T nums[], int low, int high) {
if (low >= high)
return;
int lt = low, i = low + 1, gt = high;
T value = nums[low];
while (i <= gt) {
int flag = nums[i].compareTo(value);
if (flag < 0) {
swap(nums, lt++, i++);
} else if (flag > 0) {
swap(nums, i, gt--);
} else {
i++;
}
}
sort(nums, low, lt);
sort(nums, lt + 1,high);
}
}