快速排序
1.理论基础
1.1 什么是快速排序
快速排序简称快排,它是在实践中最快的已知排序算法,根据相关测试,其平均运行时间为O(N logN)
它的基本思想是:
如果我们需要对数组S进行排序
- 如果S中的元素个数为0或1,直接返回
- 取S中任意一个元素v,并称其为key
- 以v大小为界限将S-v部分分为S1(小于v的集合),S2(大于v的集合)
- 递归的对S1,S2做上面同样的快排操作,最终得到一个有序的数组
如下图所示:
其中关键的一部就是:
如何通过key对集合S进行划分,划分的过程就是快排的关键。
并且如何取得这个key也是算法的关键
1.2 如何得到key
虽然从算法上看,无论选择那个元素作为key,最后快速排序都能正确的完成工作,但是有些选择明显能让快速排序更快的完成工作。
(一般没有经过考虑的做法是将第一个元素作为key,如果排序的数组是随机的,这个做法是可以接受的,但是,
如果数组是预排序或反序的,这样的做法绝对是很糟糕的,还有的做法是选择前两个互异元素的大者,当然这样也是不好的)
目前已知的较好的做法是: 三数中值法
选择数组左端,右端,中心位置的三个元素中的中值作为key,这种取值的做法能在一定程度上消除数组特性带来的坏影响。
1.3 快速排序是如何通过key进行划分的
我们先看一个数组 , array = {8,4,3,9,0,1},以这个数组为例
做法如下:
- 首先使用三数中值法得到array的key = 3
- 然后将key元素和最后一个位置的元素交换得到array = {8,4,1,9,0,3}
- 定义i=0,j = array.length-2
- 使用如何规则对array进行处理:
- 判断array[i]是否大于key,如果大于则i的值不变,否则i自增向后移动直到array[i]大于key
- 判断array[j]是否小于key,如果小于则j的值不变,否则j自减向前移动直到array[j]小于key
- 交换i,j位置上的值,并保持i,j不变
- 重复前面三步的操作直到i>=j
- 将i位置上的元素和key进行交换,划分完毕
具体图示如下:
2.java完整代码实现
public class intQuickSort {
public static void main(String[] args) {
int[] numbs = {5,1,1,2,0,0};
quickSort(numbs);
System.out.println(Arrays.toString(numbs));
}
/**
* 三数中值找到key并和right位置交换
* @param a 目标数组
* @param left 左位置
* @param right 右位置
* @return
*/
public static int median3(int[] a, int left,int right) {
int center = (left+right)/2;
// left为中值
if(a[left]>=a[center]&&a[left]<=a[right]||a[left]>=a[right]&&a[left]<=a[center]){
swap(a,left,right);
}
// center为中值
else if(a[center]>=a[left]&&a[center]<=a[right]||a[center]>=a[right]&&a[center]<=a[left]) {
swap(a,center,right);
}
return a[right];
}
/**
* 随机选取key
* @param a
* @param left
* @param right
* @return
*/
public static int randomKey(int[] a,int left,int right) {
Random random = new Random();
int index = random.nextInt(right-left+1)+left;
swap(a,index,right);
return a[right];
}
/**
* 交换left和right位置的元素
* @param a
* @param left
* @param right
*/
public static void swap(int[]a ,int left,int right) {
if(left<0||right<0||left>=a.length||right>=a.length){
System.out.println("left或right位置错误!!");
}
int tem = a[left];
a[left] = a[right];
a[right] = tem;
}
/**
* 2个或三个的元素就不用执行主流程了,直接进行普通的排序
* @param a
* @param left
* @param right
*/
public static void easySort(int[] a, int left, int right) {
int center = left+1;
if(a[left]>a[center]){
swap(a,left,center);
}
if(a[left]>a[right]){
swap(a,left,right);
}
if(a[center]>a[right]){
swap(a,center,right);
}
}
public static void quickSort(int[] a){
quickMain(a,0,a.length-1);
}
public static void quickMain(int[] a, int left, int right) {
if(left>=right||right<0||left>=a.length||left<0||right>=a.length) {
return;
}
//如果元素超过3个,执行快排流程
if(left+3 <= right) {
int i ,j = right-1 ,key;
//key = median3(a,left,right);
key = randomKey(a,left,right);
for(i = left;i <= j;) {
if(a[i] <= key) {
i++;
}
if(a[j] >= key){
j--;
}
if(i<j&&a[i]>key&&a[j]<key){
swap(a,i,j);
}
}
swap(a,i,right);
// 此时i位置就是中间位置
// 需要对剩下的元素进行快排操作
quickMain(a,left,i-1);
quickMain(a,i+1,right);
}
// 简单排序
else{
easySort(a,left,right);
}
}
}
时间复杂度最坏为:O(n^2) 最好为O(nlogn) 平均为:O(n log(n))
空间复杂度:O(1)
相比于前者,快速排序又优秀了许多!! 快速排序是必须要掌握的一种排序方法。