随机化优化
上面提到了最常规的排序,排序,然而在面对完全有序的数组时,快速排序的效率明显降低到了O(n^2)。快速排序划分的子数组越不平衡,快排就会降低。
快速排序采用的是分而治之的思想,如果数组完全有序的话,快排分层级提升到了n,而每次都要与所有的元素进行对比。因此在面对完全有序元素时,可对快速排序进行如下优化:
随机寻找下标,不直接从最左边开始寻找。
private static int partition(int[] arr, int l, int r) {
//原本默认从最左边开始
//新增下面一行,这回每次partion一般不会从最左边开始,划分的次数也不会降低到n
swap(arr,l, new Random().nextInt((r-l)) + l );
int v = arr[l];
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i] < v){
j++;
Hepler.swap(arr,j,i);
log.info("排序中数组{} ", arr);
}
}
Hepler.swap(arr,l,j);
log.info("排序后数组{} ", arr);
return j;
}
双路快速排序
-
定义两个下标
-
i 的位置向后扫描到(e = arr[i]) >= v,然后i停止
-
然后j往前扫描,直到arr[j] <= v ,然后停止扫描
- 这个时候交换arr[i]和arr[j]的位置
- i再往后移动下一个位置,j继续往前移动一个位置,直到i和j重合。
当出现大量重复元素的时候,重复的元素也会分配在橙色区域和紫色区域,这样划分的区域就不会特别偏向某一方。
实现:
public static void sort(int[] arr){
sort(arr,0,arr.length - 1);
}
private static void sort(int[] arr, int l, int r) {
if (l >= r){
return;
}
int p = partition2(arr,l,r);
sort(arr,l,p);
sort(arr,p + 1,r);
}
private static int partition2(int[] arr, int l, int r) {
Hepler.swap (arr,l,new Random().nextInt((r-l)) + l);
int i = l + 1;
int j = r;
int v = arr[l];
log.info("当前比较的v = {}",v);
while (true){
while (i <= r && arr[i] < v ){
i++;
}
while (j >= l && arr[j] > v){
j--;
}
if (i > j){
break;
}
log.info("交换前数组:{}",arr);
Hepler.swap(arr,i,j);
log.info("找到的i = {},j={}",i,j);
log.info("交换后数组:{}",arr);
i++;
j--;
}
Hepler.swap(arr,l,j);
log.info("一次partion的数组:{}",arr);
return j;
}
三路快速排序
之前排序都是将元素分为小于v,大于v。而三路快速排序,则将元素划分为:
- 大于v
- 小于v
- 等于v
流程:
-
定义3个会移动的索引,lt,i,gt, i指向当前要比较的元素
- 当arr[i] == v的时候,那么e直接纳入绿色的部分
- 当arr[i] < v的时候,直接交换arr[i]和arr[lt+1]的元素。然后i往后移动一个元素,lt往后移动一个元素
-
当arr[i] > v的时候,只需要把arr[i]和arr[gt - 1]的元素交换位置,但是i的位置不用变,gt的元素向前移动一个元素。
- 整个元素处理完成之后
实现:
public static void sort(int[] arr){
quickSortThreeWays(arr,0,arr.length - 1);
}
private static void quickSortThreeWays(int[] arr, int l, int r){
if (r <= l ){
return;
}
int random = new Random().nextInt(r-l + 1) + l;
Hepler.swap (arr,l,random);
int v = arr[l];
//当前元素的后一个元素
// arr[0..lt] < v
int lt = l;
// arr[gt..r] > v
//数组中最后一个元素
int gt = r + 1;
// arr[lt + 1...i] == v
//第一个元素开始
int i = l + 1;
while (true){
if (arr[i] == v){
i++;
}else if (arr[i] < v){
Hepler.swap(arr,lt + 1,i);
lt++;
i++;
}else if (arr[i] > v){
Hepler.swap(arr,i,gt - 1);
gt--;
}
log.info("当前i={} , lt = {},gt = {}",i,lt,gt);
log.info("当前数组{}",arr);
if (i >= gt){
break;
}
}
Hepler.swap(arr,l,lt );
log.info("一次quickSort数组{}",arr);
quickSortThreeWays(arr,l,lt - 1);
quickSortThreeWays(arr,gt,r);
}
最后
在《剑指Offer》上看到一句话,查找相对而言较为交单,不外乎顺序查找,二分查找,哈希查找和二叉树排序查找,在面试的时候不管使用循环还是递归,面试官都期待应聘者能信手沾来的写出完整的二分查找代码,否则连继续面试的兴趣都没有。
排序是算法中的基础,加油。