二路快排:
不管当条件是大于等于还是小于等于v,当数组中重复元素非常多的时候,那么就将数组分成了极度不平衡的两个部分,因为等于v的部分总是集中在数组的某一边。此时快排的时间复杂度再次退化为O(n^2)级别。
其中一种优化的方式便是进行双路快排。
和单路快排不同的是此时将小于v和大于v的元素放在数组的两端,那么将引用新的索引j的记录大于v的边界位置。
如图:
i索引不断向后扫描,当i的元素小于v的时候继续向后扫描,直到碰到了某个元素大于等于v。j同理,直到碰到某个元素小于等于v。
如图:
然后绿色的部分便归并到了一起,而此时只要交换i和j的位置的元素就可了,然后i++,j–就行了。
如图:
eg:二路快排分区函数实现
private static int partition2(int[] array,int l,int r) {
// 随机选取待排序数组中的任意一个元素
int randomIndex = (int) (Math.random()*(r-l+1) + l);
swap(array,l,randomIndex);
int v = array[l];
int i = l+1,j = r;
while (true) {
while (i <= r && array[i] < v) i++;
while (j >= l+1 && array[j] > v) j--;
if (i > j) {
break;
}
swap(array,i,j);
i++;
j--;
}
// 循环走完后,j索引下标为分区点位置
swap(array,l,j);
return j;
}
三路快排:
双路快排将整个数组分成了小于v,大于v的两部分,而三路快排则是将数组分成了小于v,等于v,大于v的三个部分,当递归处理的时候,遇到等于v的元素直接不用管,只需要处理小于v,大于v的元素就好了。某一时刻的中间过程如下图:
当元素e等于v的时候直接纳入绿色区域之内,然后i++处理下一个元素。
如图:
当元素e小于v的时候,只需要将元素e与等于e的第⼀一个元素交换就行了,和刚开始讲的快速排序方法类似。
同理,当大于v的时候执行相似的操作。
如图:
当全部元素处理完之后,数组便成了这个样子:
eg:三路快排实现
private static void quickSortInternal3(int[] arr,int l,int r) {
if (l >= r) {
return;
}
// 随机选取待排序数组中的任意⼀一个元素
int randomIndex = (int) (Math.random() * (r - l + 1) + l);
swap(arr, l, randomIndex);
int v = arr[l];
// arr[l+1...lt] < v
int lt = l;
// arr[lt+1...i-1] == v
int i = l + 1;// arr[gt...r] > v
int gt = r + 1;
while (i < gt) {
if (arr[i] < v) {
swap(arr, i, lt + 1);
lt++;
i++;
} else if (arr[i] > v) {
swap(arr, i, gt - 1);
gt--;
} else {
i++;
}
}
// 循环⾛走完只需要将l位置的元素与lt交换即为分区点
swap(arr, l, lt);
quickSortInternal3(arr, l, lt - 1);
quickSortInternal3(arr, gt, r);
}