从”荷兰国旗问题”到”快排”
给定一个数组arr和一个数num,请把小于num的放在数组的左边,等于num的放在中间,大于的放在右边。要求额外空间复杂度为O(1),时间复杂度为O(N).
从这道题我们可以很轻易联想到快排中的partition,但是经典快排的partition函数是没有处理”等于放在中间“这件事情的,所以我们需要用另外一个策略,实现partition。
@Test
public void test(){
int[] arr = {3,2,7,5,6,1,10};
int num = 5;
partition(arr,0,arr.length-1,num);
System.out.println(Arrays.toString(arr));
}
public static void partition(int[] arr,int left,int right,int num){
// 小于区域0-less
int less = left - 1 ;
// 当前指针
int cur = left;
// 大于区域more-arr.length
int more = right + 1 ;
while(cur < more){
if(arr[cur] < num){
// arr[cur]属于小于区
swap(arr,++less,cur++);
}else if(arr[cur] > num){
// arr[cur] 属于大于区,注意这里不需要cur++
swap(arr,--more,cur);
}else{
cur++;
}
}
}
public static void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
根据图和代码,我们可以很轻松的理清思路。
首先,我们把数组分为三个部分,一个小于区,一个等于区,一个大于区。
遍历数组arr,如果当前遍历的数小于num,则把当前数跟小于区的后一位交换,cur++,小于区扩大一位;如果当前遍历的数等于num,则cur++;如果当前遍历的数大于num,则把当前数跟大于区的前一位交换,大于区扩大一位。
为什么交换大于区的数时,cur不需要++呢?
因为如果交换过来的是一个小于num的数,此时如果cur++,则会导致这个数无法被处理移动到小于区。
改良后的快排
我们根据这个思路,改良了快排。
import org.junit.Test;
import java.util.Arrays;
/**
* 改良版快排:partition返回的是等于区的左边界和右边界
*/
public class QuickSort {
@Test
public void test(){
int[] arr = {3,2,7,5,6,1,5};
quicksort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 以 arr[right] 为轴
* @param arr
* @param left
* @param right
* @return 返回等于区域的左边界和右边界
*/
public int[] partition(int[] arr,int left,int right){
int less = left-1;
int more = right;
int cur = left;
while(cur < more){
if(arr[cur] < arr[right]){
// 小于区
swap(arr,++less,cur++);
}else if(arr[cur] > arr[right]){
// 大于区
swap(arr,--more,cur);
}else{
cur++;
}
}
// 把轴与more区的第一个交换
swap(arr,more,right);
return new int[]{less+1,more};
}
public static void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
public void quicksort(int[] arr,int left,int right) {
if(left < right){
// 从数组范围内随机选择一个数,作为轴,与right交换
swap(arr,left + (int)(Math.random()*(right - left+1)),right);
int[] partition = partition(arr, left, right);
quicksort(arr,left,partition[0]-1);
quicksort(arr,partition[1]+1,right);
}
}
public void quicksort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
quicksort(arr,0,arr.length-1);
}
为什么这个快排是”改良的“?
经典快排由于没有”等于区“,所以我们每次处理的只是一个位置的值。但是当我们有了”等于区“,那么等于区里面的所有的数都不需要参与到下一次的partition中了,这样可以节省了大量的时间。