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; } }