3-way-partition

206 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

概述

partition应该都很熟悉,是快排的核心,下面是是模板代码

public int partition(int[] arr, int left, int right) {
        int oldLeft = left; // 记录左边界
        int pivot = arr[left++];
        while(left <= right) {
            if(arr[left] <= pivot) {
                ++left;
            } else {
                swap(arr, left, right);
                --right;
            }
        }
        swap(arr, oldLeft, right);
        return right;
    }

对于数组中没有重复元素的数组,partition一定可以将数组按照pivot一分为二,pivot左边的值一定严格小于pivot,右边的值一定严格大于pivot,但是如果数组中元素重复出现比如下面的数据:

[5, 5, 3, 2, 6, 7, 8]

在运行partition之后会变成

[2, 5, 3, 5, 7, 8, 6]

选择的pivot是5(partiontion之后第二个5),pivot左侧出现了和其相等的值,原因是代码中,对于arr[left] <= pivot的情况不会做任何处理

这样对排序没有影响,但是如果我们希望partition进行更严格的划分,可以在每一轮partition之后,将nums分成三部分,如下所示 在这里插入图片描述 让nums所有和pivot相等的值都在中间部分,这样nums按照大小顺序就可以严格分成三部分,此时就需要使用到3-way-partition算法

思路

原版partition过程中,实际上遍历过程分为以下三种情况:

  • arr[left] < pivot:不操作(相当于放到pivot左边)
  • arr[left] == pivot:不操作(所以左边会出现和pivot相等的值,且可能与pivot不相邻)
  • arr[left] > pivot:将arr[left]这个较大的值换到右边去

为了实现之前所说的严格将nums划分为三部分,在arr[left] < pivot时不仅仅是将其放到pivot左边(即不操作),而是要将其放到最左边,使其尽量远离pivot,这样一轮划分下来,所有小于pivot的值都会集中在数组最左边,实现严格的划分,具体代码如下:

public int threeWayPartition(int[] arr, int left, int right) {
        int pivot = arr[left];
        int i = 0; // 记录左边小值应该放入的位置
        while(left < right) {
            if(arr[left] == pivot) {
                ++left;
            } else if(arr[left] > pivot) {
                swap(arr, left, right);
                --right;
            } else { // arr[left] < pivot
                swap(arr, i, left);
                ++i;
                ++left;
            }
        }
        return right;
    }

用i来记录可以放到的"最左边"的位置

[5, 5, 3, 2, 6, 7, 8]

划分之后

[3, 2, 5, 5, 7, 8, 6]

与pivot相等的值都会聚集到中间

例题

324. 摆动排序 II