原理:
- 从待排序区间选择一个数,作为基准值(par);
- Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
- 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。
平均时间复杂度为O(nlogn)
空间复杂度O(log(n)) ~ O(n)
递归版本思路:
当left >= right
的时候就结束递归
先找到par
然后递归(left,par-1);
递归(par+1,right);
实现代码:
import java.util.Arrays;
/**
* @author PineappleSnow
* @version 1.0
* @date 2020/6/5 15:36
*
* 基础版本快排
*/
public class QuickSort {
public static void quickSort(int[] array) {
quick(array,0,array.length-1);
}
public static void quick(int[] array, int left, int right) {
if (left >= right) return;
int par = partition(array, left, right);
quick(array, left, par-1);//递归基准左边
quick(array, par+1, right);//递归基准右边
}
public static int partition(int[] array, int low, int high) {
int tmp = array[low];//找到基准
while (low < high) {//循环终止条件
while (low < high && array[high] >= tmp) {//后面没找到小于基准的时候,high--,就是后面的大于基准
high--;
}
array[low] = array[high];//循环终止说明找到小于基准的值了,把它放到前面
while (low < high && array[low] <= tmp) {//前面没找到大于基准的时候,low++,就是前面的小于基准
low++;
}
array[high] = array[low];//循环终止说明找到大于基准的值了,把它放到后面
}
array[low] = tmp;//把 tmp 的值放回到 low 和 high 相遇的地方
return low;
}
public static void main(String[] args) {
int[] array = {9,8,7,6,5,4,3,2,1};
System.out.println(Arrays.toString(array));
quickSort(array);
System.out.println(Arrays.toString(array));
}
}
非递归版本思路:
需要用栈来保存左右两边
求出par
,(left,par-1)
为左边 (par+1,right)
为右边
当par
和左边只有一个元素的时候,或者par
左边没有元素,就不用往栈里放了
当par
和右边只有一个元素的时候,或者par
右边没有元素,就不用往栈里放了
栈空结束循环
实现代码:
import java.util.Arrays;
import java.util.Stack;
/**
* @author PineappleSnow
* @version 1.0
* @date 2020/6/6 0:02
*
* 非递归实现快排
*/
public class QuickSort3 {
public static void quickSort(int[] array) {
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = array.length - 1;
int par = partition(array,left,right);
if (par > left + 1) {
stack.push(left);
stack.push(par-1);
}
if (par < right - 1) {
stack.push(par+1);
stack.push(right);
}
while (!stack.empty()) {
right = stack.pop();
left = stack.pop();
par = partition(array,left,right);
if (par > left + 1) {
stack.push(left);
stack.push(par-1);
}
if (par < right - 1) {
stack.push(par+1);
stack.push(right);
}
}
}
public static int partition(int[] array, int low, int high) {
int tmp = array[low];
while (low < high) {
while (low < high && array[high] >= tmp) {
high--;
}
array[low] = array[high];
while (low < high && array[low] <= tmp) {
low++;
}
array[high] = array[low];
}
array[low] = tmp;
return low;
}
public static void main(String[] args) {
int[] array = {9,8,7,6,5,4,3,2,1};
System.out.println(Arrays.toString(array));
quickSort(array);
System.out.println(Arrays.toString(array));
}
}
快排的几种优化:
1.待排序区间小于一个阈值时(例如 48),使用直接插入排序
原因:对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排
截止范围:待排序序列长度N = 10,虽然在5~20之间任一截止范围都有可能产生类似的结果,这种做法也避免了一些有害的退化情形。摘自《数据结构与算法分析》Mark Allen Weiness 著
if (high - low + 1 < 10)
{
InsertSort(arr,low,high);
return;
}//else时,正常执行快排
2.选择基准值很重要,通常使用三数取中法
避免有的时候数据是有序的,把每个数据都当做基准进行比较,通过三数取中法可以有效避免。
使优化后达到这样的效果array[mid] <= array[left] <= array[right]
分析:最佳的划分是将待排序的序列分成等长的子序列,最佳的状态我们可以使用序列的中间的值,也就是第N/2个数。可是,这很难算出来,并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为枢纽元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为枢纽元。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约14%的比较次数
优化一:
import java.util.Arrays;
/**
* @author PineappleSnow
* @version 1.0
* @date 2020/6/5 15:44
*
* 通过结合插入排序进行优化
*/
public class QuickSort1 {
public static void quickSort(int[] array) {
quick(array,0, array.length-1);
}
/**
* 如果需要排序的区间小于100 那么进行插入排序 减少递归的时间空间复杂度
* @param array
* @param left
* @param right
*/
public static void quick(int[] array, int left, int right) {
if (left >= right) return;
if (right-left+1 < 100) {
insertSort(array,left,right);
return;
}
int par = partition(array, left, right);
quick(array, left, par-1);
quick(array, par+1, right);
}
public static int partition(int[] array, int low, int high) {
int tmp = array[low];
while (low < high) {
while (low < high && array[high] >= tmp) {
high--;
}
array[low] = array[high];
while (low < high && array[low] <= tmp) {
low++;
}
array[high] = array[low];
}
array[low] = tmp;
return low;
}
public static void insertSort(int[] array, int left, int right) {
for (int i = left+1; i <= right; i++) {
int j;
int tmp = array[i];
for (j = i-1; j>= 0; j--) {
if (array[j] > tmp) {
array[j+1] = array[j];
}else {
break;
}
}
array[j+1] = tmp;
}
}
public static void main(String[] args) {
int[] array = {9,8,7,6,5,4,3,2,1};
System.out.println(Arrays.toString(array));
quickSort(array);
System.out.println(Arrays.toString(array));
}
}
优化二:
import java.util.Arrays;
import java.util.Random;
/**
* @author PineappleSnow
* @version 1.0
* @date 2020/6/5 15:56
*
* 通过结合插入排序 + 三数取中 进行优化
*/
public class QuickSort2 {
public static void quickSort(int[] array) {
quick(array,0,array.length-1);
}
public static void quick(int[] array, int left, int right) {
if (left >= right) return;
//插入排序优化
if (right- left + 1 < 100) {
insertSort(array,left,right);
return;
}
//三数取中优化
three_num_mid(array,left,right);
int par = partition(array, left, right);
quick(array, left, par-1);
quick(array, par+1, right);
}
public static int partition(int[] array, int low, int high) {
int tmp = array[low];
while (low < high) {
while (low < high && array[high] >= tmp) {
high--;
}
array[low] = array[high];
while (low < high && array[low] <= tmp) {
low++;
}
array[high] = array[low];
}
array[low] = tmp;
return low;
}
public static void three_num_mid(int[] array, int left, int right) {
//达到这样的效果
//array[mid] <= array[left] <= array[right]
int mid = (right + left) / 2;
if (array[mid] > array[left]) {
swap(array,left,mid);
}
if (array[left] > array[right]) {
swap(array,left,right);
}
if (array[mid] > array[right]) {
swap(array,mid,right);
}
}
public static void insertSort(int[] array, int left, int right) {
for (int i = left; i <= right; i++) {
int j;
int tmp = array[i];
for (j = i - 1; j >= 0; j--) {
if (array[j] > tmp) {
array[j+1] = array[j];
}else {
break;
}
}
array[j + 1] = tmp;
}
}
public static void swap(int[] array, int a, int b) {
int tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
public static void main(String[] args) {
int[] array = new int[10_0000];
Random random = new Random();
for (int i = 0; i < array.length; i++) {
//有序数据
array[i] = i;
//无序数据
//array[i] = random.nextInt(10_0000);
}
long start = System.currentTimeMillis();
quickSort(array);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
public static void main1(String[] args) {
int[] array = {9,8,7,6,5,4,3,2,1};
System.out.println(Arrays.toString(array));
quickSort(array);
System.out.println(Arrays.toString(array));
}
}