在学习左神的算法课,对快速排序有了更多的理解,在此记录,以备后查
快排1.0是基于单个支点进行递归操作。大体来讲,是选择数组arr中的某一个数作为支点pivot,经过一通操作(一般叫partition),使得左边的数均小于等于pivot,右边的数均>pivot,如此一来,pivot就待在了自已排好序的位置。然后再对左边和右边分别做同样的操作,就完成了整个数组的排序
/**
* 快速排序 1.0
* @param arr
* @param left
* @param right
*/
public static void quickSort1(int[] arr, int left, int right) {
if (left >= right) {
return;
}
//进行partition操作,返回支点的位置
int pivot = partition1(arr, left, right);
//对支点左边快排
quickSort1(arr, left, pivot - 1);
//对支点右边快排
quickSort1(arr, pivot + 1, right);
}
/**
* 以arr[right]作为支点值,j为右半边的
* 左边界,curr指向当前待判断的数
* @param arr
* @param left
* @param right
*/
private static int partition1(int[] arr, int left, int right) {
int j = right, curr = left;
while (curr < j) {
if (arr[curr] <= arr[right]) {
curr++;
}else {
swap(arr,curr,--j);
}
}
swap(arr, right, j);
return j;
}
快排2.0仍然是基于单个支点的递归,不同于1.0之处,在于数组中可能有多个值跟支点pivot相等,每次partition,可以把数组分成小于pivot、等于pivot、大于pivot这三部分,避免跟pivot相等的值重复参与partition
/**
* 快速排序 2.0
* @param arr
* @param left
* @param right
*/
public static void quickSort2(int[] arr, int left, int right) {
if (left >= right) {
return;
}
//进行partition操作,返回支点的位置
int[] leftRight = partition2(arr, left, right);
//对支点左边快排
quickSort2(arr, left, leftRight[0]-1);
//对支点右边快排
quickSort2(arr,leftRight[1]+1, right);
}
/**
* 以arr[right]作为支点值,i为左半边的有边界,j为右半边的
* 左边界,curr指向当前待判断的数
* @param arr
* @param left
* @param right
* @return
*/
private static int[] partition2(int[] arr, int left, int right) {
if(left>=right){
return new int[]{-1,-1};
}
int i=left-1, j = right, curr = left;
while (curr < j) {
if (arr[curr] < arr[right]) {
swap(arr,++i,curr++);
}else if(arr[curr]==arr[right]){
curr++;
}else {
swap(arr,--j,curr);
}
}
swap(arr, right, j);
return new int[]{i+1,j};
}
快排3.0相比于2.0,增加了一个随机因子,即不再固定选择arr[right]作为支点,而是在arr[]中随机选择一个。目的是为了减少最坏情况(性能退化为O(N2))发生的概率。
/**
* 快速排序 3.0
* @param arr
* @param left
* @param right
*/
public static void quickSort3(int[] arr, int left, int right) {
if (left >= right) {
return;
}
//随机选择一个,交换到arr[right]
swap(arr,right,L+(int)Math.random()*(right-left+1));
//进行partition操作,返回支点的位置
int[] leftRight = partition2(arr, left, right);
//对支点左边快排
quickSort2(arr, left, leftRight[0]-1);
//对支点右边快排
quickSort2(arr,leftRight[1]+1, right);
}
/**
* 以arr[right]作为支点值,i为左半边的有边界,j为右半边的
* 左边界,curr指向当前待判断的数
* @param arr
* @param left
* @param right
* @return
*/
private static int[] partition2(int[] arr, int left, int right) {
if(left>=right){
return new int[]{-1,-1};
}
int i=left-1, j = right, curr = left;
while (curr < j) {
if (arr[curr] < arr[right]) {
swap(arr,++i,curr++);
}else if(arr[curr]==arr[right]){
curr++;
}else {
swap(arr,--j,curr);
}
}
swap(arr, right, j);
return new int[]{i+1,j};
}
给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
和快速排序2.0的一次partition操作基本一致,略有不同之处在于,num不是数组arr中的数,因而不需要arr[right]作为支点,右半边的左边界初始值应该从arr.length开始
/**
* 荷兰国旗问题
* @param arr
* @return
*/
private static int[] netherLandFlag(int[] arr, int num) {
if(arr==null||arr.length<2){
return arr;
}
int less=-1,more=arr.length;
int curr=0;
while (curr < more) {
if (arr[curr] < num) {
swap(arr,++less,curr++);
}else if(arr[curr]==num){
curr++;
}else {
swap(arr,--more,curr);
}
}
return new int[]{less+1,more-1};
}