大学期间学习了快速排序,下面是快速排序的代码:
public void quickSort(int[] a,int begin,int end){
if(a == null || a.length <= 1 || begin >= end)return;//递归结束条件
int L = begin;
int R = end;
int temp = a[begin];//哨兵设置为数组的第一个元素
while(L < R){
while(L < R && a[R] >= temp)R--;//从右往左,找到第一个小于temp的数
if(L < R){//L<R,说明找到了
a[L] = a[R];//替换
L++;
}
while(L < R && a[L] < temp)L++;//从左往右,找到第一个大于等于temp的数
if(L < R){//L<R,说明找到了
a[R] = a[L];//替换
R--;
}
}
//出了while循环,意味着一次排序将要完成,别忘了最后对a[i]赋值为temp
a[L] = temp;
quickSort(a,begin,L - 1);//分治
quickSort(a,L + 1, end);//分治
}
下面来总结关于《算法 第4版》中的快速排序。
思路
1)先选择一个切片v(即上文中的哨兵),一般来说选择数组的第一个元素;
2)然后,从切片位置的下一个位置开始,从前往后查找到大于等于切片v的元素,停下来,i记录索引位置;从数组末尾向前查找,找到小于等于切片v的元素,停下来,j记录索引位置;
3)交换。将索引i位置和索引j位置的元素互换位置;
4)继续从i位置到右查找到大于等于v的元素,停下来;继续从j到左查找到小于等于v的元素,停下来,交换,直到i和j相遇;
5)i,j相遇后,将v的值和a[j] 的值互换,这时将数组分成了3个部分:< v;v;>v。
6)分别对v的左半部分和v的右半部分再次执行上述操作(递归)。
下图是《算法 第4版》的例子,其中a[lo]为切分元素。
参考代码
public void quickSort(int[] arr, int low, int heigh){
if(low >= heigh)return;
int k = partition(arr,low,heigh);//将数组分为2部分,左部分小于等于k,右部分大于等于k
quickSort(arr,low,k - 1);//分治
quickSort(arr,k + 1, heigh);//分治
}
public int partition(int[] arr, int low, int heigh){
int i = low;
int j = heigh + 1;//注意这里是 heigh+1
int v = arr[low];//v每次选择第一个元素,注意这里不能写成arr[0]
while(true){//死循环
/**
这里i先 +1,j先 -1,然后再进行比较,这样就可以理解 i ,j 的初值为什么那么设置了。(i从切片v的右边开始查找,j从heigh开始查找)
**/
while(arr[++i] < v){if(i == heigh)break;}//从左到右找 >=v的数,if防越界
while(arr[--j] > v){if(j == low)break;}//从右到左找 <=v的数,if防越界
if(i >= j)break;//循环结束条件
swap(arr, i, j);//交换,然后进行下一轮循环查找(i++,j--)
}
swap(arr, low, j);//i,j相遇,交换arr[low]和arr[j]
return j;//别忘返回
}
//注意参数
public void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
注意:
partition方法中,while死循环中的第2个while中的越界判断可以省略掉:j一直往左走,当遇到小于等于v的值就停下来,而边界就是数组的首元素v,所以肯定会停下来,不用进行边界判断。
进阶
常考面试题:最小的k个数
题目描述:
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
参考代码:
public int[] getLeastNumbers(int[] arr, int k) {
if(arr == null || arr.length <= 0 || k < 0)return new int[]{};
if(k >= arr.length)return arr;
int[] res = new int[k];
quickSort(arr,0,arr.length - 1,k);
for(int i = 0; i < k; i++){
res[i] = arr[i];
}
return res;
}
public void quickSort(int[] arr,int lower,int high,int k){
int index = partition(arr,lower,high);
if(index == k)return;
else if(index > k)quickSort(arr,lower,index - 1,k);
else quickSort(arr,index + 1,high,k);
}
public int partition(int[] arr, int lower,int high){
int i = lower;
int j = high + 1;
int v = arr[lower];
while(true){
while(arr[++i] < v){if(i == high)break;}
while(arr[--j] > v){if(j == lower)break;}
if(i >= j)break;
swap(arr,i,j);
}
swap(arr,j,lower);
return j;
}
public void swap(int[] arr, int i, int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
补充
LeetCode 215题:数组中的第K个最大元素
题目描述:
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
思路分析:
求第k大的数,就是将数组排序后,索引为length-k的数,然而,用上面的思路,可以不用全部进行排序。
参考代码:
public int findKthLargest(int[] nums, int k) {
if(nums == null || nums.length == 0 || k < 0)
new RuntimeException();
int lower = 0;
int high = nums.length - 1;
k = nums.length - k;//第length-k个元素
while(lower < high){
int t = partition(nums,lower,high);
if(k == t)break;
else if(k < t)high = t - 1;
else lower = t + 1;
}
return nums[k];
}
public int partition(int[] nums,int lower,int high){
int i = lower;
int j = high + 1;
int t = nums[lower];
while(true){
while(nums[++i] < t)if(i == high)break;
while(nums[--j] > t)if(j == lower)break;
if(i >= j)break;
swap(nums,i,j);
}
swap(nums,lower,j);
return j;
}
public void swap(int[] nums,int i,int j){
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
三向切分的快速排序
public static void sort(int[] nums, int lower, int high){
if(nums == null || nums.length == 0)return;
int l = lower;
int h = high;
int i = l + 1;
if(lower < high){
int v = nums[lower];
while(l <= h){
if(nums[i] < v)swap(nums, i++, l++);
else if(nums[i] > v)swap(nums, i, h--);
else i++;
}
sort(nums, lower, l - 1);//注意:这里的递归要写在if内
sort(nums, h + 1, high);//只有长度有效的时候才会正常运行,否则出现异常
}
}
一次while遍历后的结果如下图:
双轴快速排序
public void doubleQuickSort(int[] nums, int lower, int high){
if(lower > high)return;
if(nums == null || nums.length == 0)return;
if(nums[lower] > nums[high])swap(nums, lower, high);
int p1 = nums[lower];
int p2 = nums[high];
int i = lower + 1;
int j = lower + 1;
int k = high - 1;
while(j <= k){
if(nums[j] < p1)swap(nums, i++, j++);
else if(nums[j] > p2)swap(nums, j, k--);
else j++;
}
swap(nums, lower, i - 1);//i左边都小于p1
swap(nums, k + 1, high);//k右边都大于p2
doubleQuickSort(nums, lower, i - 2);
doubleQuickSort(nums, i, k);
doubleQuickSort(nums, k + 2, high);
}