【笔记】【数据结构】快速排序

快速排序

快速排序是对冒泡排序算法的一种改进,同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。

不同的是,冒泡排序在每一轮只把一个元素冒泡到数列的一端,而快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分,这种思路叫做分治法

快排1.0

以数组当中最后一个数为基准(分界值),对剩余部分的数按照>或<=基准进行划分,划分成从左至右为<=和>两个区域,再将最后一个数与>区域的第一个数进行交换。重复此过程直至排序完成。

图解

代码实现
public class QuickSort{

    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        process(arr, 0, arr.length - 1);
    }

    //arr[l..r]排好序
    public static void process(int[] arr, int L, int R) {
        if (L < R) {
            int p = partition(arr, L, R);
            process(arr, L, p);// < 区
            process(arr, p + 1, R);// > 区 
        }
    }

    //这是一个处理arr[L...R]的函数
    //默认以arr[r]做划分,arr[r] -> p   <=p   >p
    //返回小于等于区域和大于区域的分界位置
    public static int partition(int[] arr, int L, int R) {
        int less = L - 1; // <=区右边界
        int more = R; // >区左边界
        while (L < more) { // L表示当前数的位置 arr[R] -> 划分值
            if(arr[L] <= arr[R]) { // 当前数 < 划分值
                swap(arr, ++less, L++);
            }else if (arr[L] > arr[R]) { //当前数 > 划分值
                swap(arr, --more,L);
            } else {
                L++;
            }
        }
        swap(arr, more, R);
        return new less;
    }

该算法时间复杂度为o(N^{2})

快排2.0

 荷兰国旗问题

荷兰国旗是由红白蓝3种颜色的条纹拼接而成,如上图所示。

假设这样的条纹有多条,且各种颜色的数量不一,并且随机组成了一个新的图形,新的图形可能如下图所示,但不仅仅只有这一种情况:

需要我们把这些条纹按照颜色排好,红色的在上半部分,白色的在中间部分,蓝色的在下半部分,我们把这类问题称作荷兰国旗问题。

当然我们可以把其抽象化为:

给定一个整数数组和一个值M(存在于原数组中),要求把数组中小于M的元素放到数组的左边,等于M的元素放到数组的中间,大于M的元素放到数组的右边,最终返回一个整数数组,只有两个值,0位置是等于M的数组部分的左下标值、1位置是等于M的数组部分的右下标值。

可以进一步抽象化为:

给定数组arr,将[l, r]范围内的数(当然默认是 [ 0 - arr.length - 1 ]),小于arr[r](这里直接取数组最右边的值进行比较)放到数组左边,等于arr[r]放到数组中间,大于arr[r]放到数组右边。最后返回等于arr[r]的左, 右下标值。

代码实现
/**
     * 荷兰国旗问题
     * 把数组arr中,[l, r]范围内的数,小于arr[r]放到数组最左边,等于arr[r]放到数组中间,大于arr[r]放到数组最右边
     *
     * @return 返回等于arr[r]的左, 右下标值
     */
    public static int[] netherlandsFlag(int[] arr, int l, int r) {
        if (l > r) {
            return new int[]{-1, -1};
        }
        if (l == r) {
            return new int[]{l, r};
        }
        // 小于arr[r]的右边界,从L的左边一位开始
        int less = l - 1;
        // 大于arr[r]的左边界,从r开始,即当前右边界里已经有arr[r]
        int more = r;
        // 当前正在比较的下标
        int index = l;
        // 不能与 大于arr[r]的左边界 撞上
        while (index < more) {
            if (arr[index] < arr[r]) {
                // 小于时,将当前位置的数和小于arr[r]的右边界的下一个位置交换
                // 当前位置后移一位
                swap(arr, index++, ++less);
            } else if (arr[index] == arr[r]) {
                // 等于时,当前位置后移一位即可
                index++;
            } else {
                // 大于时,当前位置的数和大于arr[r]的左边界的前一个位置交换
                // 当前位置不动
                swap(arr, index, --more);
            }
        }
        // 将arr[r]位置的数和大于arr[r]的左边界的数交换
        // 此时整个数组就按照 小于、等于、大于arr[r] 分成了左中右三块
        swap(arr, more, r);

        // 最后整个数组中,等于arr[r]的左右边界分别是less + 1, more
        return new int[]{less + 1, more};
    }

图解

代码实现
public class QuickSort{

    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        process(arr, 0, arr.length - 1);
    }

    //arr[l..r]排好序
    public static void process(int[] arr, int L, int R) {
        if (L < R) {
            int[] p = partition(arr, L, R);
            process(arr, L, p[0] - 1);// < 区
            process(arr, p[1] + 1, R);// > 区 
        }
    }

    //这是一个处理arr[L...R]的函数
    //默认以arr[r]做划分,arr[r] -> p   <p   ==p    >p
    //返回等于区域(左边界,有边界),所以返回一个长度为2的数组res, res[0] res[1]
    public static int[] partition(int[] arr, int L, int R) {
        int less = L - 1; // <区右边界
        int more = R; // >区左边界
        while (L < more) { // L表示当前数的位置 arr[R] -> 划分值
            if(arr[L] < arr[R]) { // 当前数 < 划分值
                swap(arr, ++less, L++);
            }else if (arr[L] > arr[R]) { //当前数 > 划分值
                swap(arr, --more,L);
            } else {
                L++;
            }
        }
        swap(arr, more, R);
        return new int[] { less + 1, more };
    }

该算法时间复杂度为o(N^{2})

快排3.0

首先设定一个分界值(此处采用随机抽取的数),通过该分界值将数组分成左中右三部分。

(1)小于arr[r],与小于区的后一个位置交换,当前位置后移;

(2)等于arr[r],当前位置直接后移;

(3)大于arr[r],与大于区的前一个位置交换,当前位置不动(交换到此位置的数还没比较过,所以不动)。

遍历完后,arr[r]和大于区的最左侧位置交换。

最后返回,此时小于区的后一个位置,大于区的位置,即是最后的等于arr[r]的左, 右下标值。

然后,左边和右边的数据可以独立排序。对于左侧的数据,又可以取一个分界值,将该部分数据分成左中右三部分,同样在左边放较小值,中间放等于值,右边放较大值。右边数据也做同样的处理。

重复上述过程,可以看出,这是一个递归过程。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

图解

代码实现
public class QuickSort{

    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        process(arr, 0, arr.length - 1);
    }

    //arr[l..r]排好序
    public static void process(int[] arr, int L, int R) {
        if (L < R) {
            swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
            int[] p = partition(arr, L, R);
            process(arr, L, p[0] - 1);// < 区
            process(arr, p[1] + 1, R);// > 区 
        }
    }

    //这是一个处理arr[L...R]的函数
    //默认以arr[r]做划分,arr[r] -> p   <p   ==p    >p
    //返回等于区域(左边界,有边界),所以返回一个长度为2的数组res, res[0] res[1]
    public static int[] partition(int[] arr, int L, int R) {
        int less = L - 1; // <区右边界
        int more = R; // >区左边界
        while (L < more) { // L表示当前数的位置 arr[R] -> 划分值
            if(arr[L] < arr[R]) { // 当前数 < 划分值
                swap(arr, ++less, L++);
            }else if (arr[L] > arr[R]) { //当前数 > 划分值
                swap(arr, --more,L);
            } else {
                L++;
            }
        }
        swap(arr, more, R);
        return new int[] { less + 1, more };
    }

该方法中涉及到随机行为,对于时间复杂度的证明较为复杂,涉及到概率论等知识,故只需记住此处时间复杂度为o(N*log^{N})即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值