快速排序
快排的思路是:选定最左边的数为参考值,从第二个数开始与参考值进行比较,小的集中在参考值的左边,大的不用管
package cn.stu.test;
import java.util.Arrays;
public class SortQuick {
public static void main(String[] args) {
int[] arr1 = {12,34,62,23,14,52,36,78,96};
int[] sort = sortQuick(arr1,0,arr1.length-1);
System.out.println(Arrays.toString(sort));
}
public static int[] sortQuick(int[] arr,int left,int right) {
if(right<left)
return arr;
int i = sort(arr,left,right);
sortQuick(arr, left, i-1);
sortQuick(arr, i+1, right);
return arr;
}
public static int sort(int[] arr, int left, int right){
int temp = arr[left];
int j =left;
for(int i=left+1;i<=right;i++){
if(arr[i]<temp){
swap(arr, i, j+1);
j++;
}
}
swap(arr, left, j);
return j;
}
public static void swap(int[] arr, int i, int j){
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
问题一:简单优化?
优化一:递归到个数比较少的时候,可以采用插入排序
//增加插入函数
public static void sort_insert(int[] arr, int left, int right) {
for (int i = left + 1; i <= right; i++) {
int j = i;
while ((j >= left + 1) && (arr[j] < arr[j - 1])) {
swap(arr, j-1, j);
j--;
}
}
}
//修改快排递归的退出条件
public static int[] sortQuick(int[] arr,int left,int right) {
if(right-left <=4){
sort_insert(arr, left, right);
return arr;
}
int i = sort(arr,left,right);
sortQuick(arr, left, i-1);
sortQuick(arr, i+1, right);
return arr;
}
问题二:近乎有序的数组使用快排的时候,运行效率退化成了O(n²),怎么优化?
优化二:不要固定使用左边第一个元素作为参考值,可以通过一个随机数,选出数组一个随机元素,再将这个随机元素与左边第一个元素交换位置。
public static int[] sortQuick(int[] arr,int left,int right) {
if(right-left <=4){
sort_insert(arr, left, right);
return arr;
}
/*产生随机的参考元素*/
Random r = new Random(System.nanoTime());
int num = r.nextInt(1000)%(right-left)+left;
swap(arr, left, num);
int i = sort(arr,left,right);
sortQuick(arr, left, i-1);
sortQuick(arr, i+1, right);
return arr;
}
问题三:当数组里面有大量的重复数据的时候,快排又O(n²)了
优化三:可以采用双路快排的方式,将重复的数据分散到两个子数组,提高平衡
方式一:是前面的快排的拓展,前面是从一边入手来寻找参考点的位置,这里是从两边入手,两个索引不同的时候就交换元素,两个索引相同的时候代表找到合适的位置。
public static int sortTwoWay1(int[] arr, int left, int right) {
int i = left;
int j = right;
int temp = arr[i];
i = i+1;
while(j>=i){
while(j>=i&&arr[j]>temp){
j--;
}
while(j>=i&&arr[i]<temp){
i++;
}
if(j>i){
swap(arr, i, j);
j--;
i++;
}
}
swap(arr, left, j);
return j;
}
方式二:跟方法一不大一样的地方就是判断的时候,这里采用直接交换的方式,免去判断的操作,不过这样反而增加了一些额外的交换操作,可能消耗一定的时间。
public static int sort_2(int[] arr, int left, int right) {
int i = left;
int j = right;
int temp = arr[i];
while(j>i){
while(j>i&&arr[j]>temp){
j--;
}
arr[i] = arr[j];
while(j>i&&arr[i]<temp){
i++;
}
arr[j] = arr[i];
}
arr[j] = temp;
return j;
}
还是问题三?
优化四:采用三路快排的方式,将大量重复的元素集中在一起,迭代循环的时候就直接跳过这部分的处理,从而加快处理速度。
因为要返回两个索引,所以直接写进快排的函数里面,不再封装。
public static int[] sortQuick(int[] arr,int left,int right) {
if(right < left){
return arr;
}
int i = left;
int temp = arr[left];
int j = right+1;
int k = i+1;
while(k<j){
if(arr[k]>temp){
swap(arr,k,j-1);
j--;
}else if(arr[k]<temp){
swap(arr,k,i+1);
i++;
k++;
}else{
k++;
}
}
swap(arr,i,left);
sortQuick(arr, left, i-1);
sortQuick(arr, j, right);
return arr;
}