引例:
给定一个数组nums,和一个数n,请把小于等于n的数放在数组的左边,大于n的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度 O(N)
只是要求把数组分成两块,小于等于区域和大于区域
实现如下:
上述过程可分为两种情况:
- 当nums[i] <= n时,将nums[i]与小于等于区域的下一个数进行交换,小于等于区域向右扩展一位,并且i++
- 当nums[i] > n时,i++
直到i越界结束,可以理解为 小于等于区域 向右扩展推挤 大于区域
代码:
public static void partition(int[] nums, int n ){
int less = -1;//小于区域右边界
int i = 0;
while(i < nums.length){
if(nums[i] <= n){
swap(nums,++less,i++);
}else{
i++;
}
}
}
public static void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
荷兰国旗问题实现类似:
问题:给定一个数组nums,和一个数n,请把小于n的数放在数组的左边,等于n的数放在数组的中间,大于n的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
荷兰国旗问题与上述问题的区别就是要把nums分为 小于区域、等于区域、大于区域 三部分,
类比上述问题,可以让小于区域向右推挤,再让大于区域向左推挤,这样中间自自然就是等于区域了。
过程如下:
- nums[i] < n,将小于区域的下一个数与num[i]交换,小于区域右扩,i++;
- num[i] == n,i++;
- nums[i] > n,将大于区域的前一个数与nums[i]交换,大于区域左扩,i不变,这里i不变的原因是nums[i]是由右边区域的数交换过来的,并没有进行比较与n,所以i不能右移。
代码如下:
public void partition(int[] nums,int n){
int less = -1;//初始小于区域右边界
int more = nums.length;//大于区域初始左边界
int i = 0;
while(i < more){
if(nums[i] < n){
swap(nums,++less,i++);
}else if(nums[i] > n){
swap(nums,--more,i);
}else{
i++;
}
}
}
public static void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
由荷兰国旗问题引出快排的实现
荷兰国旗问题可以将数组nums中的某个重复出现的数放在一起(等于区域),其实在经过荷兰国旗问题将数组划分为三个区域后,中间的(等于区域)的位置就是排序后的位置,因为它左边全是小于它的数,右边全是大于它的数。
可以理解为经过一次的partition(荷兰国旗问题处理)我们就确定了一批数(这个数出现了多次)/ 一个数(这个数只出现了一次)的位置,那么我们把partition后的 小于区域 和 大于区域 送入递归,让他们继续进行partition,每次partition都确定一批数 / 一个数,这样当小于区域和大于区域只有一个数时,就已经完成了排序。这也就是快排的过程。
只不过我们要对partition的返回值加以修改,让它返回 等于区域 的左右区间,用于递归时来分割递归区域。
代码实现:
public static void quickSort(int[] nums,int L,int R){
if(L < R) {
int[] p = partition(nums, L, R);
quickSort(nums, L, p[0] - 1);
quickSort(nums, p[1] + 1, R);
}
}
public static int[] partition(int[] nums,int L,int R){
int less = L - 1;
int more = R + 1;
int i = L;
while(i < more){
if(nums[i] < nums[R]){
swap(nums,++less,i++);
}else if(nums[i] > nums[R]){
swap(nums,--more,i);
}else{
i++;
}
}
//返回等于区域的左右区间
int[] amount = {less + 1,more - 1};
return amount;
}
public static void swap(int[] nums, int i ,int j) {
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
}
可是上述快排的时间复杂度是O(N^2),导致N^2的原因是我们每次都默认nums的最后一个数作为划分,这样我们可以人为 的使数组进行两次递归时的权重严重失衡,使得成为了N^2的算法,我们只要使每次的划分值的选取是一个随机事件,最终的时间复杂度就降到了O(NlogN),(具体证明过于复杂,这里不做证明,给出传送门>>>快排时间复杂度的证明)下面是最终的完整代码:
public static void quickSort(int[] nums,int L,int R){
if(L < R) {
//随机选取划分值放到数组末尾
swap(nums,L + (int)Math.random() * (R - L + 1),R);
int[] p = partition(nums, L, R);
quickSort(nums, L, p[0] - 1);
quickSort(nums, p[1] + 1, R);
}
}
public static int[] partition(int[] nums,int L,int R){
int less = L - 1;
int more = R + 1;
int i = L;
while(i < more){
if(nums[i] < nums[R]){
swap(nums,++less,i++);
}else if(nums[i] > nums[R]){
swap(nums,--more,i);
}else{
i++;
}
}
int[] amount = {less + 1,more - 1};
return amount;
}
public static void swap(int[] nums, int i ,int j) {
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
}