快速排序之partition分区问题版本、netherlandsFlag荷兰国旗问题版本,三次改进以及一个常规快排版本!

今天分享的是关于快速排序的思路以及三种版本的迭代优化

我们先来介绍partition问题 多图预警!! 所有的算法都经过对数器核对,可以放心使用
已知一个数组arr,要求我们以随机的一个数作为标准把数组分为以下两个区域,小于等于区、大于区,可以不用考虑各区数字的排序 下例我们假设这个数为4
partition1

我们设置一个小于等于区(下称≤区),它的起始位置是-1;再设置一个变量index,它指向的是第一个数

partition2

然后我们从左往右遍历,当index 大于 arr.length-1 时跳出循环,有以下两种情况

  1. 如果arr[i] <= num 则当前数(arr[i])和 “≤区”的下一个数交换,“≤区”右扩,i++
  2. 如果arr[i] > num 则 i++

我们来进行下一步分析

  1. 3是小于4的,符合第一个逻辑,则 arr[i] 即 arr[0] 和 “≤区”的下一个数进行交换,即自己和自己交换,然后 i++ ,“≤区”右扩。此时 i = 1,“≤区”在0位置
    partition3
  2. 5是大于4的,符合第二个逻辑,则 i++。此时 i = 2,“≤区”在0位置
    partition4
  3. 2是小于4的,符合第一个逻辑,则 arr[i] 即 arr[2] 和 “≤区”的下一个数进行交换,即arr[1]和arr[2]进行交换,然后 i++ ,“≤区”右扩。此时 i = 3,“≤区”在1位置
    partition5
  4. 4是等于4的,符合第一个逻辑,则 arr[i] 即 arr[3] 和 “≤区”的下一个数进行交换,即arr[2]和arr[3]进行交换,然后 i++ ,“≤区”右扩。此时 i = 4,“≤区”在2位置
    partition6
  5. 8是大于4的,符合第二个逻辑,则 i++。此时 i = 5,“≤区”在2位置
    partition7
  6. 1是小于4的,符合第一个逻辑,则 arr[i] 即 arr[5] 和 “≤区”的下一个数进行交换,即arr[3]和arr[5]进行交换,然后 i++ ,“≤区”右扩。此时 i = 6,“≤区”在3位置
    partition8
  7. 7是大于4的,符合第二个逻辑,则 i++。此时 i = 7,“≤区”在3位置
    partition9
  8. 4是等于4的,符合第一个逻辑,则 arr[i] 即 arr[7] 和 “≤区”的下一个数进行交换,即arr[4]和arr[7]进行交换,然后 i++ ,“≤区”右扩。此时 i = 8,跳出循环,“≤区”在4位置
    partition10
    可以看到,我们成功的分离出了这两个数组,这个方法可以运用到很多问题。partition的代码实现如下,为了方便起见,我们选的的基准数是每一个数组最右边的数,大家可以仔细体会下面的代码。
public static int partition(int[] arr, int left, int right) {
        if (left > right) {
            return -1;
        }
        if (left == right) {
            return left;
        }
        int less = left - 1;
        int index = left;
        while (index < right) {
            if (arr[index] <= arr[right]) {
                swap(arr, index, ++less);
            }
            index++;
        }
        swap(arr, ++less, right);
        return less;
    }

public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

仔细观察,可以发现我们的partition方法是返回了“≤区”所在的位置的,这个位置的值实际上就是我们已经排好序的位置,我们根据这个位置,对其左边的数组和右边的数组再进行partition操作,就是我们快排的第一个版本了。

 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 left, int right) {
        if (left >= right) {
            return;
        }
        int m = partition(arr, left, right);
        process1(arr, left, m - 1);
        process1(arr, m + 1, right);
    }

接下来我们介绍荷兰国旗netherlandsFlag划分问题,它可以进一步改进我们的快排

已知一个数组arr,要求我们以随机的一个数作为标准把数组分为以下三个区域,小于区、等于区、大于区,可以不用考虑各区数字的排序 下例我们假设这个数为4

netherlandsFlag1
类似的,我们设置一个小于区(下称”<区“),它的起始位置是-1,一个大于区(下称”>区“),它的起始位置是 arr.length 再设置一个变量index,它指向的是第一个数

netherlandsFlag2
我们假定指定的基准数是4
然后我们从左往右遍历,当index 大于 arr.length-1 时跳出循环,有以下三种情况

  1. 如果arr[i] < num 则当前数(arr[i])和”<区“的下一个数交换,“<区”右扩,i++
  2. 如果arr[i] > num 则 当前数(arr[i])和”>区“的前一个数交换,“>区”左扩,i停在原地(要点)
  3. 如果arr[i] == num 则 i++

大家可以根据上面的规则在纸上画一画~~~一定要耐心画一画!!得出结果的时候会觉得很舒服

) 3 5 2 4 8 1 7 4 (
3 ) 5 2 4 8 1 7 4 ( 判断条件:3<4
3 ) 4 2 4 8 1 7 ( 5 判断条件:5>4
3 ) 4 2 4 8 1 7 ( 5 判断条件:4=4
3 2 ) 4 4 8 1 7 ( 5 判断条件:2<4
3 2 ) 4 4 8 1 7 ( 5 判断条件:4=4
3 2 ) 4 4 7 1 ( 8 5 判断条件:8>4
3 2 ) 4 4 1 ( 7 8 5 判断条件:7>4
3 2 1 ) 4 4 ( 7 8 5 判断条件:1<4

可以看到,我们成功的分离出了这三个数组,netherlandsFlag的代码实现如下,为了方便起见,我们选的的基准数同样选择的是每一个数组最右边的数。

/**
     * arr[L ...... R] 荷兰国旗划分问题 以arr[right]为划分值
     * <arr[R] ==arr[R] >arr[R]
     * 空间复杂度O(1) 时间复杂度O(n)
     */
    public static int[] netherlandsFlag(int[] arr, int left, int right) {
        if (left > right) {
            return new int[]{-1, -1};
        }
        if (left == right) {
            return new int[]{left, right};
        }
        // <区 有边界
        int less = left - 1;
        //因为我们以arr[right]为界限 所以暂时不动
        // >区 左边界
        int more = right;
        int index = left;
        while (index < more) {
            if (arr[index] == arr[right]) {
                index++;
            } else if (arr[index] < arr[right]) {
                swap(arr, index++, ++less);
            } else if (arr[index] > arr[right]) {
                swap(arr, index, --more);
            }
        }
        //L......Less Less+1......more-1 more......R-1 R
        swap(arr, more, right);
        return new int[]{less + 1, more};
    }

一定要注意好边界问题,仔细想一想。
此时我们可以注意到,netherlandsFlag返回的是等于区域的数组范围,与partition问题同样的,这中间的数组是已经排好序了的数组,我们可以继续用netherlandsFlag对其左边数组和右边数组排序,就可以对我们快排效率进一步优化(因为中间少区分了一个范围上的数)。下面是快排第二版:

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

    public static void process2(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }
        int[] equalArea = netherlandsFlag(arr, left, right);
        process2(arr, left, equalArea[0] - 1);
        process2(arr, equalArea[1] + 1, right);
    }

快排第三版,就是我们不再选择最右侧的数作为基准,而是随机选的一个数,这样可以让时间复杂度变成O(n*logn),这是一个数学上的期望问题,感兴趣的可以去查阅相关资料。

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

    public static void process3(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }
        swap(arr, (int) (left + (Math.random() * (right - left + 1))), right);
        int[] equalArea = netherlandsFlag(arr, left, right);
        process3(arr, left, equalArea[0] - 1);
        process3(arr, equalArea[1] + 1, right);
    }

以上就是我们快排的三个迭代版本!
以下再给出我之前在网上看到的一个版本和对数器(校验算法是否正确的),测试了十万次,保证算法一定是正确的!

public static void quickSort4(int[] arr, int left, int right) {
        if (left < right) {
            int base = arr[left];
            int i = left;
            int j = right;
            while (i < j) {
                while (arr[i] < base && i < right) {
                    i++;
                }
                while (arr[j] > base && j > left) {
                    j--;
                }
                if (i <= j) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                    i++;
                    j--;
                }
            }
            if (left < j) {
                quickSort4(arr, left, j);
            }
            if (right > i) {
                quickSort4(arr, i, right);
            }
        }
    }

    /**
     * test
     */
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) (Math.random() * (maxSize + 1))];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }

    public static boolean isEqual(int[] arr1, int[] arr2) {
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int testTime = 100000;
        int maxSize = 20;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            int[] arr3 = copyArray(arr1);
            int[] arr4 = copyArray(arr1);
            quickSort1(arr1);
            quickSort2(arr2);
            quickSort3(arr3);
            quickSort4(arr4, 0, arr4.length - 1);
            if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) {
                succeed = false;
                printArray(arr1);
                printArray(arr2);
                printArray(arr3);
                break;
            }
            if (!isEqual(arr1, arr4)) {
                succeed = false;
                printArray(arr1);
                printArray(arr4);
                break;
            }
        }
        System.out.println(succeed ? "bingo" : "fail");
    }

如有不当肯定大家指出,谢谢~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值