import java.util.*;
public class Main {
public static void main(String[] args)
{
int[] num={38,5,18,45,8,62,19,27};
quickSort(num,0,num.length-1);
for(int i=0;i<num.length;i++)
System.out.println(num[i]);
}
public static void quickSort(int[] num,int left,int right)
{
if(left<right)
{
int middle=sort(num,left,right);
quickSort(num,left,middle-1);
quickSort(num,middle+1,right);
}
}
public static int sort(int[] num,int left,int right)
{
int tmp=num[left];
while(left<right)
{
while(left<right&&num[right]>=tmp)
right--;
if(left<right)
num[left++]=num[right];
while(left<right&&num[left]<=tmp)
left++;
if(left<right)
num[right--]=num[left];
}
num[left]=tmp;
return left;
}
}
快速排序的三个步骤:
1.选择基准,作为分割序列的比较依据。
2.进行分割操作,将序列以改基准在序列中的位置分成两个子序列,左边序列元素值均小于基准,右边序列元素值均大于基准。
3.递归对两个子序列进行快速排序直到序列为空或只有一个元素。
选择基准的方式:
对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列。
三种选择基准的方法:
1、取序列的第一个或者最后一个元素作为基准
缺点:若数组有序,此时的分割效果非常差,每次划分只能使待排序序列减一,快速排序沦为冒泡排序,时间复杂度O(N2)
2、随机选取基准:取待排序列中任意一个元素作为基准,将选择好的基准元素与low位置元素互换位置,此时就可以和普通的快排一样调用划分函数。
缺点:数字全相等的情况下,时间复杂度依然是O(n2)。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。
3、三数取中
一般是取low、middle、high三个位置上元素的中值作为基准
缺点:同样处理不了重复数组
三中优化方式:
1、当待排序序列长度分割到一定大小后,使用插入排序,因为对于很小和部分有序的数组,快排不如插排好。一般当待排序序列长度等于10时,就使用插入排序。
2、一次分割结束后,将于基准元素相等的元素聚在一起,继续下次分割时,不用再对与key相等的元素分割
public class Main {
public static void main(String[] args)
{
int[] num={1,5,4,8,3,6,7,2,12,9,34,27,54,43,18,20,-1,-5,13,17,-22};
quickSort(num,0,num.length-1);
for(int i=0;i<num.length;i++)
System.out.println(num[i]);
}
public static void quickSort(int arr[],int low,int hight)
{
int first=low;
int last=hight;
int left=low;
int right=hight;
int leftLen=0;
int rightLen=0;
//待排序序列长度小于10时,直接使用插入排序
if(hight-low+1<10)
{
InsertSort(arr,low,hight);
return;
}
int key = SelectPivotMedianOfThree(arr,low,hight);//使用三数取中法选择枢轴
while(low<hight)
{
while(low<hight&&arr[hight]>=key)
{
if(arr[hight]==key)
{
swap(arr[hight],arr[right]);
right--;
rightLen++;
}
hight--;
}
arr[low]=arr[hight];
while(low<hight&&arr[low]<=key)
{
if(arr[low]==key)
{
swap(arr[low],arr[left]);
left++;
leftLen++;
}
low++;
}
arr[hight]=arr[low];
}
arr[low]=key;
int i=low-1;
int j=first;
while(j<left&&arr[i]!=key)
{
swap(arr[i],arr[j]);
i--;
j++;
}
i=low+1;
j=last;
while(j>right&&arr[i]!=key)
{
swap(arr[i],arr[j]);
j--;
i++;
}
quickSort(arr,first,low-1-leftLen);
quickSort(arr,low+1+rightLen,last);
}
public static void InsertSort(int[] arr,int low,int height)
{
for(int i=low+1;i<=height;i++)
{
int tmp=arr[i];
int j;
for(j=i;j>low&&arr[j-1]>tmp;j--)
{
arr[j]=arr[j-1];
}
arr[j]=tmp;
}
}
public static int SelectPivotMedianOfThree(int[] arr,int low,int hight)
{
int middle=(low+(hight-low)/2);
if(arr[middle]>arr[hight])
swap(arr[middle],arr[hight]);
if(arr[low]>arr[hight])
swap(arr[low],arr[hight]);
if(arr[low]<arr[middle])
swap(arr[low],arr[middle]);
return arr[low];//low存放中间元素的值
}
public static void swap(int a,int b)
{
int tmp=a;
a=b;
b=tmp;
}
}
3、优化递归操作
快排函数在函数尾部有两次递归操作,我们可以对其使用尾递归优化
void QSort(int arr[],int low,int high)
{
int pivotPos = -1;
if (high - low + 1 < 10)
{
InsertSort(arr,low,high);
return;
}
while(low < high)
{
pivotPos = Partition(arr,low,high);
QSort(arr,low,pivot-1);
low = pivot + 1;
}
}