起因:前面我们写的快排,在划分(分区)的时候无论是一遍单向扫描,双向扫描还是三指针分区法都是把第一个元素定义为主元,所以说划分的时候可能并不均匀(不是一半一半划分的),最坏的情况下可能导致时间复杂度变为O(n^2),而快排理想的是O(n*lgn).
法一:三点中值法
public static int patitiion(int[] arr,int start,int end) {
//优化,在start,end,mid中找主元
int midindexof = start+((end-start)>>1);
int midindexvalueof = -1;
int zhuyuan;
if(arr[start]>arr[end]&&arr[end]>arr[midindexof]) {
zhuyuan = arr[end];
Swap.swap(arr, start, end);
}else if(arr[end]>arr[start]&&arr[start]>arr[midindexof]) {
zhuyuan = arr[start];
}else {
zhuyuan = arr[midindexof];
Swap.swap(arr, start, midindexof);
}
//左指针
int left = start+1;
int right = end;//右指针
while(left<=right) {
//左侧大于主元就停下,特殊情况会越界
while(left<=right&&arr[left]<=zhuyuan) {
left++;
}
//右侧小于等于主元就停下
while(left<=right&&arr[right]>zhuyuan) {
right--;
}
if(left<right)
Swap.swap(arr, left, right);//停下后元素交换
}
Swap.swap(arr, start, right);
return right;
}
法二:绝对中值法
找这个数组的真正的中间值:通过把这个数组每五个元素一个区间,进行插入排序,然后找到每个区间的中间值存放到一个数组中去,然后对这个数组进行插入排序,找到中间值,就是整个数组的中间值了,也是主元。
import java.util.Arrays;
public class 快排优化之绝对中值法 {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] brr = new int[]{9,5,6,8,4};
quicksort(brr,0,brr.length-1);
System.out.println(Arrays.toString(brr));
}
public static void quicksort(int[] arr, int start, int end) {
if(start<end) {//递归的出口
int q = partition(arr,start,end);//找到主元数组下标
quicksort(arr,start,q-1);//对左边进行快排,
quicksort(arr,q+1,end);//对右边进行快排
}
}
//找到主元的下标,并把小于主元的放左边,大于主元的放右边
public static int partition(int arr[],int start,int end) {
int tz = zhuyuan(arr,start,end);
for(int i = start;i<=end;i++) {
if(arr[i]==tz) {
swap(arr, start, i);
}
}
int zuyuan = arr[start];//主元初始化
int sp = start+1;//左侧扫描指针
int bigger = end;//右侧末指针
//两个指针交错说明数组已经扫描完了
while(sp<=bigger) {
if(arr[sp]<=zuyuan) {//扫描指针所指元素比主元小或者相等
sp++;
}
else {//扫描指针所指元素比主元大
swap(arr,sp,bigger);
bigger--;
}
}
swap(arr, start, bigger);//把主元放到bigger这个位置,这样bigger左边都小于主元,右边都大于主元
return bigger;
}
//找主元的大小
public static int zhuyuan(int[] arr,int start,int end) {
int size = end - start+1;//参与排序这个区间的长度
//每五个元素一组
int groupSize = (size%5==0)?(size/5):(size/5+1);
//存储各个小组的中值
int midValue[] = new int[groupSize];
//对每一组进行插入排序,找到每一组中的中值,并存入数组中去
for(int i = 0;i<groupSize;i++) {
//因为最后一组不知道有多少个元素,所以要分开讨论
if(i == groupSize-1) {//最后一组
insertSort(arr,start+5*i,end);//对最后一组进行插入排序
midValue[i] = arr[(start+5*i+end)/2];
}else {
insertSort(arr,start+5*i,start+5*i+4);
midValue[i] = arr[(start+5*i+end)/2];
}
}
insertSort(midValue,0,midValue.length-1);
return midValue[midValue.length/2];
}
//插入排序
public static void insertSort(int[] arr,int start, int end) {
for(int i=start;i<end+1;i++) {
int jilu = i;//用来记录i的值,以便防止while循环过后I的值改变
while(i>=0&&i<end&&arr[i]>arr[i+1]) {//通过while循环逐步把去调整要插入的位置
swap(arr,i,i+1);
i--;
}
i=jilu;//用来恢复i的取值
}
}
public static void swap(int[] arr,int a,int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
注意:
当待排序列较短的的时候,插入排序比快排更好
经过分析我们知道(假设序列长度为n)
插入排序要执行n*(n-1)/2次
而快排执行n*lgn+n次
所以当元素个数小于等于8的时候插入排序更快