荷兰国旗问题:
给定一个数组和一个数num,请把小于num的数放在数组左边,把等于num的数放在中间,把大于num的数放在数组右边。要求额外空间复杂度O(1),时间复杂度O(n)
思路:
先定义一个left指针,比原数组arr左边界小1。然后再定义一个right指针,比原数组右边界大1。然后还有一个遍历数组的指针index。
然后依次遍历arr[index],如果小于num,则将left加1,然后把left位置的数和index位置交换。这一步是什么意思呢?left的含义就是保存比num小的数的边界,如果index遍历到一个比num小的数时,此时把left下一位的数和index位置交换,然后left右移一位吗,index加1。这也是为什么初始设定left比原数组arr左边界小1,right比原数组右边界大1,因为初始边界内应该不包含任何数。 同理,如果arr[index]比num大,则right下一位和index交换,然后right左移一位。但是,此时index不加1,这点是left和right移动时的区别。因为此时right换到index位置的数还没进行过和num对比,从前面换过来的数是已经比较过的,应该==num,而后面数还没遍历到 当arr[index]等于num,则只有index++。
public static int[] DutchFlag(int[] arr,int num,int l,int r){
int left=l-1;
int right=r+1;
int index=l;
while(index<right){
if(arr[index]<num){
swap(arr,++left,index);
index++;
}else if(arr[index]>num){
swap(arr,index,--right);
}else {
index++;
}
}
return new int[]{left+1,index-1};
}
private static void swap(int[] arr,int l1,int l2) {
int temp=arr[l1];
arr[l1]=arr[l2];
arr[l2]=temp;
}
用荷兰国旗问题优化快速排序问题:
经典快速排序问题,是每次选取第一个或者最后一位,然后递归实现。这里讲荷兰国旗问题,就是为了优化经典快速排序问题。
优化一:
可以看到,经典快速排序,每次只有一个元素到达最终位置,而荷兰国旗问题,可以看到,最终返回的是中间等于某个数的一个区间,相当于,荷兰国旗问题,每次可以将一组相同的数放到最终位置上,而不是经典快排的一个数。
优化二:
第二点优化其实就和荷兰国旗问题没什么关系了。经典快排,要么每次从第一个位置选择元素,要么从最后一个位置选择元素,如果原数组有序,那么时间复杂度就是n²。如果改为随机数组中任意一个数的话,(int)Math.random()*(right-left+1),那么就变成一个概率问题,有人经过严格数学推导,随机的话,时间复杂度就是nlogn,能有效避免n²:
代码:
public static void quickSort(int[] arr,int left,int right){
if(left>=right){
return;
}
//(int)Math.random()*(right-left+1)随机选取数组中一个数
int[] ar = DutchFlag(arr, arr[left+(int)Math.random()*(right-left+1)], left, right);
quickSort(arr,left,ar[0]-1);
quickSort(arr,ar[1]+1,right);
}
public static int[] DutchFlag(int[] arr,int num,int l,int r){
int left=l-1;
int right=r+1;
int index=l;
while(index<right){
if(arr[index]<num){
swap(arr,++left,index);
index++;
}else if(arr[index]>num){
swap(arr,index,--right);
}else {
index++;
}
}
return new int[]{left+1,index-1};
}
private static void swap(int[] arr,int l1,int l2) {
int temp=arr[l1];
arr[l1]=arr[l2];
arr[l2]=temp;
}