快速排序
分治算法
思路:
- 在整个待排序区间,确定一个基准值(pivot)
- 遍历整个待排序区间,将所有数据与基准值进行比较,最终达到 partition
比基准值小的(可以包含等于)在基准值左边
比基准值大的(可以包含等于)在基准值右边 - 用同样的方法处理左右两个小的待排序区间,直到
1)小区间内没有数据了(size==0)
2)小区间已经有序(size == 1)
大的框架
递归
//快排
public static void quickSort(int[] array){
quickSortInternal(array,0,array.length-1);
}
public static void quickSortInternal(int[] array,int left,int right){
if(left>right){
return ;
}
//1.确定基准值:array[right]作为基准值
//2. 遍历,小的左,大的右
int pivotIndex=partition(array,left,right);
//分出两个区间
//[left,pivotIndex-1]
//[pivotIndex+1,right]
//3.治
quickSortInternal(array,left,pivotIndex-1);
quickSortInternal(array,pivotIndex+1,right);
}
非递归
自己使用栈,记录待排序区间的左右边界。
public static void quickSortNoR(int[] array){
Stack<Integer> stack=new Stack<>();
stack.push(array.length-1);
stack.push(0);
while(!stack.isEmpty()){
int left=stack.pop();
int right=stack.pop();
if(left>=right){
continue;
}
int pivotIndex=partition1(array,left,right);
//[left,pivotIndex-1]
//[pivotIndex+1,right]
stack.push(right);
stack.push(pivotIndex+1);
stack.push(pivotIndex-1);
stack.push(left);
}
}
重点:partition(排数据)
1.Hover 法
1)两个引用,在待排序区间,一个(less)从前往后遍历,直到遇到比基准值大的;另一个(great)从后往前遍历,直到遇到比基准值小的;交换两个值
2) 循环条件:less < great
3) 基准值:数组最后一个值
交换 less 下标处的值和基准值
private static int partition1(int[] array,int left,int right){
int pivot=array[right];
int less=left;
int great=right;
while(less<great){
while(less<great&&array[less]<=pivot){
less++;
}
while(less<great&&array[great]>=pivot){
great--;
}
swap(array,less,great);
}
swap(array,right,less);
return less;
}
2.挖坑法
与Hover 法类似,相当于把基准值的位置空出来,从前遍历,把比基准值大的放入空缺位置,而空出另一位置;从后往前遍历,把比基准值小的放到刚空的前面空位。最后把基准值放入空位。
private static int partition(int[] array,int left,int right){
int pivot=array[right];
int less=left;
int great=right;
while(less<great){
while(less<great&&array[less]<pivot){
less++;
}
array[great]=array[less];
while(less<great&&array[great]>=pivot){
great--;
}
array[less]=array[great];
}
array[less]=pivot;
return less;
}
3.前后下标
private static int partition3(int[] array,int left,int right){
//[left,less) 小于pivot
//[less,i) >=pivot
int less=left;//蓝色箭头
int pivot=array[right];
for(int i=left;i<right;i++){
if(array[i]<pivot){
swap(array,i,less);
less++;
}
}
swap(array,less,right);
return less;
}
4.把和基准相等的情况单独处理
- 循环条件 i < great i 从前往后遍历
- 三种情况:1)与基准值相等:i 继续往后走
2)小于基准值:与 less 处值(小于基准值区间如上图)交换,less向后走,i 向后走(相当于区间变大)
3)大于基准值:与 great 处值交换
注意:找好区间
private static int[] partition(int[] array,int left,int right){
int pivot=array[right];
int less=left;
int great=right;
int i=left;
while(i<great){
if(array[i]==pivot){
i++;
}else if(array[i]<pivot){
swap(array,less,i);
i++;
less++;
}else{
while(i<great&&array[great]>pivot){
great--;
}
swap(array,i,great);
}
}
return new int[] {less,great-1};
}
时间复杂度 - - O(n) * 二叉树的高度
1)最好/平均:O(n*log(n))
2)最坏:O(n^2)
一次 partition 的时间复杂度 O(n)
高度: O(log(n) ~ O(n) )
空间复杂度
递归调用栈
最好:O(log(n))
最坏:O(n)
稳定性
不稳定
基准值
1)最边上
2)随机取
3)多数取中(三数取中) 五数取中