双轴快排

243 阅读2分钟

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

什么是双轴快排

  • 双轴快排是快速排序的一种优化方案,在Arrays.sort中被使用,单轴快排也就是常说的普通的快速排序

  • 对于普通的快速排序,是基于递归和分治的,时间复杂度最坏是O(n2),最好和平均情况为O(nlogn).

  • 普通快排的思路是,每次在待排序序列中找一个数(通常最左侧多一点),然后在这个序列中将比他小的放它左侧,比它大的放它右侧,由于每次排序只有一个基准所以也叫单轴快排

双轴快排的实现方式

取两个中心点pivot1,pivot2,且pivot≤pivot2,可将序列分成三段:x<pivot1、pivot1≤x≤pivot2,x<pivot2

image.png

双轴和单轴的对比

但单轴很多时候可能会遇到较差的情况就是当前元素可能是最大的或者最小的,这样子元素就没有被划分区间,快排的递推T(n)=T(n-1)+O(n)从而为O(n2).

双轴就是选取两个主元素理想将区间划为3部分,这样不仅每次能够确定元素个数增多为2个,划分的区间由原来的两个变成三个,最坏最坏的情况就是左右同大小并且都是最大或者最小,但这样的概率相比一个最大或者最小还是低很多很多,所以双轴快排的优化力度还是挺大的。

代码

package com.improve;

import java.util.Arrays;

public class Code01_DualPivotQuickSort {
    public static void main(String[] args) {
        Code01_DualPivotQuickSort d = new Code01_DualPivotQuickSort();
        //测试--------------------------------------------------
        int n = (int) (Math.random() * 10000);
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = (int) (Math.random() * 10000);

        }
        int[] arr1 = arr.clone();
        Arrays.sort(arr);
        d.dualPivotQuickSort(arr1);
        boolean flag = true;
        for (int i = 0; i < n; i++) {
            if (arr[i] != arr1[i]) {
                System.out.println(Arrays.toString(arr));
                System.out.println(Arrays.toString(arr));
                System.out.println("arr[i]=" + arr[i]);
                System.out.println("arr1[i]=" + arr1[i]);
                flag = false;
                break;
            }
        }
        System.out.println("flag=" + flag);
        //测试--------------------------------------------------

        int[] nums = {2, 4, 5, 6, 2, 9, 13, 11};

        d.dualPivotQuickSort(nums);
        System.out.println(Arrays.toString(nums));
    }

    /**
     * 双轴快排
     *
     * @param nums
     */
    public void dualPivotQuickSort(int[] nums) {
        dualPivotQuickSort(nums, 0, nums.length - 1);
    }

    public void dualPivotQuickSort(int[] nums, int start, int end) {
        if (start < end) {
            if (nums[start] > nums[end]) {
                swap(nums, start, end);
            }
            int pivot1 = nums[start];
            int pivot2 = nums[end];
            int cur = start + 1;
            int left = start; //左边部分(包括left本身)
            int right = end;    //右边部分

            OUT_LOOP:
            while (cur < right) {
                if (nums[cur] < pivot1) { //属于左边部分的值
                    swap(nums, cur++, ++left);
                } else if (nums[cur] <= pivot2) { // pivot1 <= nums[cur] <=pivot2
                    cur++;
                } else {
                    while (nums[--right] > pivot2) {    //right指针的右部分表示的是右边部分,只要满足条件就一直向左走
                        if (cur >= right) {
                            break OUT_LOOP;
                        }
                    }
                    if (nums[right] < pivot1) { //说明是属于左边部分的数
                        swap(nums, cur, right);  //这边为什么不直接swap(nums,++left,right)?
                        //因为left++到cur部分可能是已经划分好了的中间部分的值,所以不要去动这个部分
                        swap(nums, cur, ++left);

                    } else { //说明是属于中间部分的数
                        swap(nums, cur, right);//去尝试cur中有没有符合右边范围的数
                    }
                    cur++;
                }
                
            }
            swap(nums, start, left);
            swap(nums, end, right);//让原来作为标准的两个数回到自己的位置
            dualPivotQuickSort(nums, start, left - 1);
            dualPivotQuickSort(nums, left + 1, right - 1);
            dualPivotQuickSort(nums, right + 1, end);
        }
    }

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