快排

快排

例1

给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组右边,要求额外空间复杂度O(1),时间复杂度O(N)

解法:

0、我们指定一块区域,称之为小于等于区,在这个区域里面,存储着小于num的数

1、遍历arr数组,如果arr[i]<=num,arr[i]和 在小于等于区的下一个数交换,然后小于等于区右扩,i++

2、遍历数组arr,如果arr[i] > num,i++,跳过

3、越界了停

例2:例1的派生 荷兰国旗问题

给定一个数组arr和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边,要求额外空间复杂度O(1),时间复杂度O(N)

解法:

0、指定2块区域,一个是小于区,另一个是大于区

1、遍历arr数组,如果arr[i] < num,arr[i]和 在小于区的下一个数交换,然后小于区右扩i++

2、遍历数组arr,如果arr[i] = num,i++,跳过

3、遍历数组arr,如果arr[i] > num,arr[i]和大于区的前一个交换,然后大于区右扩,i不变

4、当i和大于区的边界相重合的时候,遍历停止

//在arr[L...R]上进行荷兰国旗问题,以arr[R]作为指定的num划分值
//小于arr[R]放在左边,等于arr[R]的放在中间,大于arr[R]放在右边
//返回等于区域的左右 边界
public static int[] netherlandFlag(int[] arr, int L, int R){
    //如果左边比右边还大直接错误了,返回-1
    if(L > R){
        return new int[]{-1, -1};
    }
    //如果左右边界相等,直接返回左右边界
    if(L == R){
        return new int[]{L, R};
    }
    //小于区右边界
    int less = L -1;
    //大于区左边界,这里先把arr[R]算作已经进入右边界,先不考虑它移动,因为他是划分值
    int more = R;
    //让当前循环的数字下标从最左端进入
    int index = L;
    //当前循环的数字下标不能超过最右端的
    while(index<more){
        //假如当前的数字等于制定的数字
        if(arr[index] == arr[R]){
            //直接跳过这个数字,它的位置不需要动
            index++;
        }else if(arr[index < arr[R]){
            //假如当前的数字小于指定的数字,那么他要和小于区的下一个进行互换,并且小于等于区向后移动一个,最后进入下一个数字进行判断
            swap(arr,index++,++less);
        }else{
            //假如当前的数字大于指定的数字,他和大于区的前一个进行交换,并且大于区向前移动一位,但是当前的数字不能在往前移动,因为当前index所在的数字是从原大于区调换过来的,还没有进行判断
            swap(arr,index,--more);
        }
     }
     //把所有的都判断完后,让最后这个指定数字,和大于区的第一位进行互换,就得到了荷兰国旗算法以后的数字
     swap(arr ,more, R);
     //返回等于区域的边界
     //【左(小于区的下一个less+1)右(原本应该是大于区的前一个,但是大于区的第一个跟指定的数交换了,所以就是大于区的死第一个more)】
     //[小于区(L...less),等于区(less+1,more),大于区(more+1...R)]
     return new int[] {less + 1, more};
}
public static void swap(int[] arr, int i, int j){
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

快排1.0版本 O(N^2)

假设有一个数组,这个数组的最后一位是X

(1)整体进行进行partition

假如小于等于X的,我们把它放在数列的左端小于等于区比X大的放在右边大于区,最后把X和大于区的第一个互换(也就是小于等于区的最后后一个数),这样我们就把X排好了,但左侧小于等于区里,很有可能还有X,我们下一步继续partition左侧的小于等于区

 

(2)继续对部分进行partition

左边小于等于区继续进行partition,设这个区域的最后一个数是X',和第一步一样搞定X'在中间

右边大于等于区继续进行partition,设这个区域的最后一个数是X‘’,和第一步一样搞定X'‘在中间

........周而复始得到最后的结果

 

public static void quickSort1(int[] arr){
    if(arr == null || arr.length < 2)
        return;
    process1(arr, 0, arr.length-1);
}
//递归的快排
public static void process1(int[] arr, int L, int R){
    if(L >= R)
        return;
    //在arr[L..R]上 进行partition方法 arr[R]=X 将arr变成:[ <=X, X, >X]
    //返回值M表示X最后落在哪里了
    int M = partition(arr, L, R);
    process1(arr, L, M-1);
    process1(arr, M+1, R);
}

 

快排2.0版本 O(N^2)

直接利用荷兰国旗算法,直接将等于X的全部固定在数组中间,剩下继续利用递归,把左右两块区域的X' X''固定住

 

public static void quickSort2(int[] arr){
    if(arr == null && arr.length == 0)
        return;
    process2(arr, 0, arr.length - 1);
}
public static void process2(int[] arr, int L, int R){
    if(L>R)
        return;
    //得到是中间等于区域的左右边界下标
    int[] equalArea = netherlandFlag(arr, L, R);
    //继续从最左边L,到小于区域的最右边,中间等于区域的左边前一位(equalArea[0])下标-1递归
    process2(arr, L, equalArea[0] - 1);
    //继续从中间等于区域的右边的下一位(equalArea[1])下标+1,到最右大于区域边R递归
    process2(arr, equalArea[1] + 1, R)
}

快排3.0版本 O(N*logN)

我们在数组中随机选一个数作为X,并且把它放到数组的最后一个上,然后和快排2.0执行一样的操作

public static void quickSort3(int[] arr){
    if(arr == null && arr.length == 0)
        return;
    process3(arr, 0, arr.length - 1);
}
public static void process3(int[] arr, int L, int R){
    if(L > R)
        return;
    //在数组中,L-R的范围上随机选一个数字,作为X
    int X = (int) (Math.random() * (R - L + 1)); 
    //并且把这个X放在
    swap(arr, X, R);
    int[] equalArea = netherLandFlag(arr, L, R);
    process3(arr, 0, equalArea[0] - 1);
    process3(arr, equalArea[1] + 1, R);
}

posted on 2022-08-18 22:40  老菜农  阅读(88)  评论(0编辑  收藏  举报

导航