快排
例1
给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组右边,要求额外空间复杂度O(1),时间复杂度O(N)
解法:
0、我们指定一块区域,称之为小于等于区,在这个区域里面,存储着小于num的数
1、遍历arr数组,如果arr[i]<=num,arr[i]和 在小于等于区的下一个数交换,然后小于等于区右扩,i++
2、遍历数组arr,如果arr[i] > num,i++,跳过
3、越界了停
例2:例1的派生 荷兰国旗问题
给定一个数组arr和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边,要求额外空间复杂度O(1),时间复杂度O(N)
解法:
0、指定2块区域,一个是小于区,另一个是大于区
1、遍历arr数组,如果arr[i] < num,arr[i]和 在小于区的下一个数交换,然后小于区右扩i++
2、遍历数组arr,如果arr[i] = num,i++,跳过
3、遍历数组arr,如果arr[i] > num,arr[i]和大于区的前一个交换,然后大于区右扩,i不变
4、当i和大于区的边界相重合的时候,遍历停止
//在arr[L...R]上进行荷兰国旗问题,以arr[R]作为指定的num划分值
//小于arr[R]放在左边,等于arr[R]的放在中间,大于arr[R]放在右边
//返回等于区域的左右 边界
public static int[] netherlandFlag(int[] arr, int L, int R){
//如果左边比右边还大直接错误了,返回-1
if(L > R){
return new int[]{-1, -1};
}
//如果左右边界相等,直接返回左右边界
if(L == R){
return new int[]{L, R};
}
//小于区右边界
int less = L -1;
//大于区左边界,这里先把arr[R]算作已经进入右边界,先不考虑它移动,因为他是划分值
int more = R;
//让当前循环的数字下标从最左端进入
int index = L;
//当前循环的数字下标不能超过最右端的
while(index<more){
//假如当前的数字等于制定的数字
if(arr[index] == arr[R]){
//直接跳过这个数字,它的位置不需要动
index++;
}else if(arr[index < arr[R]){
//假如当前的数字小于指定的数字,那么他要和小于区的下一个进行互换,并且小于等于区向后移动一个,最后进入下一个数字进行判断
swap(arr,index++,++less);
}else{
//假如当前的数字大于指定的数字,他和大于区的前一个进行交换,并且大于区向前移动一位,但是当前的数字不能在往前移动,因为当前index所在的数字是从原大于区调换过来的,还没有进行判断
swap(arr,index,--more);
}
}
//把所有的都判断完后,让最后这个指定数字,和大于区的第一位进行互换,就得到了荷兰国旗算法以后的数字
swap(arr ,more, R);
//返回等于区域的边界
//【左(小于区的下一个less+1)右(原本应该是大于区的前一个,但是大于区的第一个跟指定的数交换了,所以就是大于区的死第一个more)】
//[小于区(L...less),等于区(less+1,more),大于区(more+1...R)]
return new int[] {less + 1, more};
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
快排1.0版本 O(N^2)
假设有一个数组,这个数组的最后一位是X
(1)整体进行进行partition
假如小于等于X的,我们把它放在数列的左端小于等于区,比X大的放在右边大于区,最后把X和大于区的第一个互换(也就是小于等于区的最后后一个数),这样我们就把X排好了,但左侧小于等于区里,很有可能还有X,我们下一步继续partition左侧的小于等于区
(2)继续对部分进行partition
对左边小于等于区继续进行partition,设这个区域的最后一个数是X',和第一步一样搞定X'在中间
对右边大于等于区继续进行partition,设这个区域的最后一个数是X‘’,和第一步一样搞定X'‘在中间
........周而复始得到最后的结果
public static void quickSort1(int[] arr){
if(arr == null || arr.length < 2)
return;
process1(arr, 0, arr.length-1);
}
//递归的快排
public static void process1(int[] arr, int L, int R){
if(L >= R)
return;
//在arr[L..R]上 进行partition方法 arr[R]=X 将arr变成:[ <=X, X, >X]
//返回值M表示X最后落在哪里了
int M = partition(arr, L, R);
process1(arr, L, M-1);
process1(arr, M+1, R);
}
快排2.0版本 O(N^2)
直接利用荷兰国旗算法,直接将等于X的全部固定在数组中间,剩下继续利用递归,把左右两块区域的X' X''固定住
public static void quickSort2(int[] arr){
if(arr == null && arr.length == 0)
return;
process2(arr, 0, arr.length - 1);
}
public static void process2(int[] arr, int L, int R){
if(L>R)
return;
//得到是中间等于区域的左右边界下标
int[] equalArea = netherlandFlag(arr, L, R);
//继续从最左边L,到小于区域的最右边,中间等于区域的左边前一位(equalArea[0])下标-1递归
process2(arr, L, equalArea[0] - 1);
//继续从中间等于区域的右边的下一位(equalArea[1])下标+1,到最右大于区域边R递归
process2(arr, equalArea[1] + 1, R)
}
快排3.0版本 O(N*logN)
我们在数组中随机选一个数作为X,并且把它放到数组的最后一个上,然后和快排2.0执行一样的操作
public static void quickSort3(int[] arr){
if(arr == null && arr.length == 0)
return;
process3(arr, 0, arr.length - 1);
}
public static void process3(int[] arr, int L, int R){
if(L > R)
return;
//在数组中,L-R的范围上随机选一个数字,作为X
int X = (int) (Math.random() * (R - L + 1));
//并且把这个X放在
swap(arr, X, R);
int[] equalArea = netherLandFlag(arr, L, R);
process3(arr, 0, equalArea[0] - 1);
process3(arr, equalArea[1] + 1, R);
}