快速排序
算法思想:(分治思想)
1.在序列中任意位置选取一个基准点,将比基准点小的值全部放到基准点的左边,比基准点大的值全部放到基准点的右边;每次排序过后 基准点一定在它的最终位置;
2,以基准点为基点 进行左右分割 ,将数组继续分割成小的序列并在小序列中重复过程1,
所以,我们可以选取第一个位置/或者最后一个位置为基准点(这里选取第一个)下标设置成l
基准值为value
tip: 在和归并比较时,优先用快速排序;
随机快排
public static void main(String[] args) {
int[] array=new int[]{2,1,4,3,1,6,5};
quickSort(array);
for(int i:array){
System.out.print(i+" ");
}
}
/**
* 快速排序
* @param array
*/
public static void quickSort(int[] array){
int n=array.length;
if(n<=1){
return ;
}else{
quickSortInternal(array,0,n-1);
}
}
public static void quickSortInternal(int[] array,int l,int r){
if(l>=r){
return ;
}else{
int q=partition(array,l,r);//一次分割后 基准点一定在它的最终位置上
quickSortInternal(array,l,q-1);
quickSortInternal(array,q+1,r);
}
}
private static int partition(int[] array,int l,int r){
int value=array[l];//基准值的选取 这里选取第一个
//array[l+1……j]<value的区间,刚开始时 此区间没有元素,所以j初始值为l
int j=l;
//arra[j+1……i-1]>value的区间,刚开始时也没有元素,但是开始也一定在l+1,在基准值之后
//因为i以后是没有进行比较的空间
int i=l+1;
for(;i<=r;i++){
if(value>array[i]){
swap(array,j+1,i);//j是<value的最后一个元素,所以只能和j+1进行交换,小于value的空间扩大一个
j++;//将j的下标向后移一个
}
}
swap(array,j,l);//此时全部<value 的空间一定在<=j的区间里,
return j;
}
private static void swap(int[] array,int i,int j){
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
分析:时间复杂度:快排如果每次都近乎分割成大小相近的空间,那么该算法的时间复杂度就是O(nlogn);
(因为它的空间分割也是递归得来的)
但是上面选取分区点(基准值)的方法有bug,如果待排序数组是一个近乎有序的数组,呢么我们分区的次数就会近乎为n 那么快排就会退化成O(n^2)时间复杂度的算法;
空间复杂度 :O(1),与归并排序相比 没有开辟新空间
稳定性:不问定排序 5 16 4 2 5 和基准点的选取有关,如果 该序列 选取第一个位置作为基准点就是不稳定的 ,最后一个5,
会被换到前面,
优化:
让每次基准点(分区点)的选取都是随机的,就可以避面 左右两端区间严重不均衡 让分层趋向于O(n)导致快排退化成O(n^2)
我们只用改变 partition 方法即可,
private static int partition2(int[] array,int l,int r){
int randomIndex=(int) (Math.random()*(r-l+1))+l;//生成【l-r】的随机数
swap(array,l,r);
int j=l,i=l+1,value=array[l];
for(;i<=r;i++){
if(array[i]<value){
swap(array,j+1,i);
j++;
}
}
swap(array,j,l);
return j;
}
二路快排
首先,上面我们已经对数组基本有序的的情况作出了优化,但是还有一种情况**,如果数组中重复的元素过多导致分组两端数组长度严重不均衡**,数组分割次数依旧会是n, 这时 还是会退化成O(n^2)时间复杂度的算法
将重复元素交换位置来实现左右两个数组都有元素,避免分组不平衡;
所以 我们采用将和基准点相同的元素放到基准点的左右两端
private static int partition3(int[] array,int l,int r){
int randomIndex=(int) (Math.random()*(r-l+1)+l);//产生随机下标
swap(array,randomIndex,l);
//array[l+1……i]<value array[j……r]>value
int i=l+1,j=r,value=array[l];
while(true){
while(array[i]<value&&i<=r)i++;//碰到大于value的值 停下
while(array[j]>value&&j>=l)j--;//碰到小于value的值停下
if(i>j){//这时表明所有元素都已经遍历完,退出循环
break;
}
swap(array,i,j);//否则,交换两个元素
i++;
j--;
}
swap(array,j,l);//j就是<value的最后一个值交换
return j;
}
三路快排(大量出现等于基准值的情况),在重复元素比较多的情况
private static int partition4(int[] array,int l,int h){
//三路快排
int value=array[l];
//array[l+1,lt]<value
int lt=l;
//array[gt,r]>value
int gt=h+1;//从后向前遍历,这时,>value的空间一个都没有,所以是h+1
//array[lt+1,i-1]==value
int i=l+1;//从中间元素开始排序
while(i<gt){//i>=gt,说明所有元素都已经遍历完了
if(array[i]<value){
swap(array,lt+1,i);
lt++;//<value的空间增加一个
i++;//i++,向后走
}else if(array[i]>value){
swap(array,gt-1,i);
gt--;//>value的空间增加一个
}else{
i++;//==value的空间增加一个
}
}
swap(array,l,lt);
return gt;
}
优化
我们知道当数据集小的时候,直接插入排序就 变得非常有效率
所以,我们可以让数据集到达非常小的时候进行插入排序
public static void insert(int[] array,int l,int r){
for(int i=l+1;i<=r;i++){
int j=i-1,value=array[i];
while(j>=l&&value<array[j]){
array[j+1]=array[j];
j--;
}
array[j+1]=value;
}
}
public static void quickSortInternal(int[] array,int l,int r){
if(l>=r){
insert(array,l,r)
return ;
}else{
int q=partition(array,l,r);//一次分割后 基准点一定在它的最终位置上
quickSortInternal(array,l,q-1);
quickSortInternal(array,q+1,r);
}
}
总结
快速排序是一种很快的算法,但是有些时候,一些数据在快排这反而不如别的排序,这时我们对快排进行了优化,
二路,三路快排,目的是为了解决数据集近乎有序或者数据集重复太多的情况下,因为分割的不平衡导致的分割次数的增加 从而时间复杂度退化的问题:
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定排序