经典排序算法[1]-快排

本文深入探讨了快速排序算法,其核心思想源于荷兰国旗问题。通过详细阐述三边界问题,解释如何以给定数值划分数组,实现数据分区。在快排中,通过对数组的递归操作,将数组分为小于、等于和大于指定值的三个区域,从而达到排序的目的。文章还讨论了优化策略,如随机选取元素作为基准,避免最坏情况下的时间复杂度。
摘要由CSDN通过智能技术生成

经典排序算法[1]-快排

1、荷兰国旗问题

  荷兰国旗问题又称为三边界问题,先来看看数据分区问题:假设有一个数组arr,给定一个数,将数组中小于该数和大于该数的数据各放在数组的两边

  算法流程:

假设数组[5,3,7,5,3,4,1] 给定的数是3
  • 假设数组最后会被划分成[小于等于3的区域][大于3的区域],构造小于等于3的数据区的右边界,初始右边界是-1,扫描数组进行如下比较
     如果 扫描的数据<=给定数据
        当前数据和小于等于3区域的数据右边界的下一个元素交换,并移动边界【也就是边界+1】,意味着小于等于3的数据区变大了
     否则 不做操作    
     
     扫描下一个数据
  • 1)、扫描数据5时,5>3 直接扫描下一个
  • 2)、扫描3时,3<=3,此时小于等于3的区域右边界是-1,先将右边界+1变成0,再将3和0位置数据交换变成
[3,5,7,5,3,4,1]
  • 3)、重复该操作,不断扩充小于等于3的数据边界,最终能达到数据分散在两侧,但是不是有序的

假设需要分为三个区[小于区][等于区][大于区],其中:小于区的所有元素<等于区的所有元素<大于区的所有元素,解法和上面的相似,算法流程是:

[3,2,4,0,4,6,7] 给定数字是4 //实际情况这个给定数字可以随机从数组选择
  • 需要设置小于区的右边界初始值为-1,大于区的左边界初始值为数组的长度,扫描的时候两个边界向中间扩展,以上面的例子为例,起始左边界为-1 右边界为8,只是当数据和右边界交换后,需要重复判断交换后的数
     如果 扫描的数据<给定数据
        当前数据和小于区域的数据右边界的下一个元素交换,并移动边界【也就是边界+1】
        扫描下一个数据
     如果 扫描的数据>给定数据
        当前数据和大于区域的数据左边界的上一个元素交换,并移动边界【也就是边界+1】
        下一次继续扫描比较当前位置的数 //英文当前位置的数为交换回来的数,还未和给定数字比较
     否则
         扫描下一个数据

代码实现:**

public class ThreeBound {

    /**
     * 数据分区,假设有一个数组,给定一个数,将数组中小于该数的的大于该数的数据各放在数组的两边
     * 要求 空间复杂度O(1) 时间复杂度 O(N)
     *
     * 假设数组[5,3,7,5,3,4,1] 给定的数是3
     *
     * 1、构造小于等于3的数据边界,初始边界是-1,扫描数组进行如下比较
     *       如果扫描的数据<=给定数据
     *       当前数据和小于等于3的数据边界的下一个元素交换,并移动边界【也就是边界+1】
     *       扫描下一个数据
     *
     *  1、扫描数据5时,5>3 直接扫描下一个
     *  2、扫描3时,3<=3,此时小于等于3的区域边界是-1,所以3需要和0位置数据交换变成
     *     [3,5,7,5,3,4,1]
     *  3、重复该操作,不断扩充小于等于3的数据边界,最终能达到数据分散在两侧,但是不是有序的
     *
     *
     * 假设需要分为三个区,大于 等于 小于
     * 则需要设置小于的边界初始值为-1,大于区的初始边界为数组的长度,扫描的时候边界向中间扩展,只是当数据和右边界交换后,需要重复判断交换后的数
     * 例如
     * [3,2,4,0,4,6,7] 给定数字是4
     *
     * 起始左边界为-1 右边界为8
     * 扫描到6时,需要和7交换,交换后为[3,2,4,0,4,7,6],由于7原来在6后面,未被扫描,所以扫描指针等从现在的7位置开始扫描才能保证不漏数据
     *
     *
     */

    //实现三边界问题,荷兰国旗问题
    //以数组最后一个元素划分
    public  static  int[] ThreeBound(int[] data,int L,int R){

        if(L > R){
            return new int[]{-1,-1};
        }

        if(L == R){
            return new int[]{L,R};
        }


        int less = L-1;
        int more = R; //先固定最后一个元素,扫描后将其和大于区的边界交换即可
        int index = L;

        while(index<more){
            if(data[index] == data[R]){
                index++;
            }else if(data[index]<data[R]){
                swap(data,index++,++less);
            }else{
                swap(data,index,--more);
            }
        }

        swap(data,more,R); //
        return new int[]{less+1,more};
    }

    private static void swap(int[] data, int index, int i) {

        if(i==index){
            return;
        }else {
            data[index] = data[index] ^ data[i];
            data[i] = data[index] ^ data[i];
            data[index] = data[index] ^ data[i];
        }
    }

}
2、 快排

  快排的整体思想和荷兰国旗是一样的,整体的思路如下:

    /**
     * 快排的思想
     *
     *
     * 借用的是三边界问题,假设现在有一个数组,我们以数组最后一个元素进行边界划分
     * 得到的是
     * [小于区][等于区][大于区][末尾元素]
     * 再将末尾元素和大于区的第一个元素交换,得到
     * [小于区域][等于区][末尾元素][大于区]
     * 
     * 可以看到入如果将原数组进行排序,等于区在排好序数组的位置就是这样,所以可以递归小于区和大于区就可以完成排序
     *
     * 优化点:
     * 有可能出现的数组是7654321这种逆序数组,如果都拿最后一个元素,最后的时间复杂度是O(N^2),所以递归的时候先随机选择一个元素
     * 和末尾交换,再调用三边界问题得到边界,再递归,转化成等概率问题,时间复杂度就是O(NlogN)
     *
     */

  代码实现:

public class QuickSort {


    //实现三边界问题,荷兰国旗问题
    //以数组最后一个元素划分
    public  static  int[] ThreeBound(int[] data,int L,int R){

        if(L > R){
            return new int[]{-1,-1};
        }

        if(L == R){
            return new int[]{L,R};
        }


        int less = L-1;
        int more = R; //先固定最后一个元素,扫描后将其和大于区的边界交换即可
        int index = L;

        while(index<more){
            if(data[index] == data[R]){
                index++;
            }else if(data[index]<data[R]){
                swap(data,index++,++less);
            }else{
                swap(data,index,--more);
            }
        }

        swap(data,more,R); //
        return new int[]{less+1,more};
    }

    private static void swap(int[] data, int index, int i) {

        if(i==index){
            return;
        }else {
            data[index] = data[index] ^ data[i];
            data[i] = data[index] ^ data[i];
            data[index] = data[index] ^ data[i];
        }
    }

    public static void quickSort(int[] data,int L,int R){

        if(L>=R){
            return;
        }

        int length = R-L+1;

        int index = new Random().nextInt(length);
        swap(data,L+index,R);
        int[] tmpResult = ThreeBound(data, L, R);

        quickSort(data,L,tmpResult[0]-1);
        quickSort(data,tmpResult[1]+1,R);


    }


    public static void main(String[] args) {

        int[] data = {5, 4, 3, 2, 1, 2, 3, 4, 5, 3};

//        process(data,0,data.length-1);

//        int[] res = ThreeBound(data, 0, data.length - 1);

//        System.out.println(Arrays.toString(res));

        quickSort(data,0,data.length-1);

        System.out.println("-----------------------------------");

        System.out.println(Arrays.toString(data));

    }


}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值