概述
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相等的值都会聚集到中间