在8大排序算法之间的时间复杂度,辅助空间以及稳定性情况如下表1:
表1
一:快速排序。
快速排序的核心在鱼partition函数部分,如何选择pivot分割点,以及如何围绕分割点进行分割;
法一:如下代码
/**
* 快速排序法
*/
public class QuickSort {
//运用分治思想,按照每次的中间值分开两部分后,每部分继续递归去分为两部分排序
public void Quick_Sort(int A[],int start,int end){
int position = QCCore01(A, start, end);
if(position>start){
Quick_Sort(A,start,position-1);
}
if(position<end){
Quick_Sort(A,position+1,end);
}
}
//快排核心部分,选取一个分界值,然后把数组分为两部分,运用前后两指针对向移动方法
private int QCCore01(int A[],int start,int end){
int key = A[start];//这里取的是第一个元素作为分界值
while(start<end){
//右指针先左移,直到遇到“小”值
while(A[end]>=key&&start<end){
end--;
}
//把“小”值交换到左边,把分界值转换过来
if(start<end){
int temp = A[end];
A[end] = key;
A[start] = temp;
}
//左指针右移,直到遇到“大”值
while(A[start]<=key&&start<end){
start++;
}
//把“大”值交换到右边,把分界值转换过来
if(start<end){
int temp = A[start];
A[start] = key;
A[end] = temp;
}
}
return start;//返回中间值的标号
}
}
该方法比较常用,即选择每次排序段数组第一个元素作为pivot元素,然后两遍依次向中间夹逼,每次都是与pivot元素交换;
法二:代码如下
/**
* 快速排序法
*/
public class QuickSort {
//运用分治思想,按照每次的中间值分开两部分后,每部分继续递归去分为两部分排序
public void Quick_Sort(int A[],int start,int end){
int position = QCCore02(A, start, end);
if(position>start){
Quick_Sort(A,start,position-1);
}
if(position<end){
Quick_Sort(A,position+1,end);
}
}
//快排核心部分,运用双指针同时从前向后,产生大小差值,并根据差值数替换。
private int QCCore02(int A[],int start, int end){
int small = start;//记录“小”值的数目,big遇到“大”值不增加,遇到“小”值才增加
int big = start+1;//代表当前指针去遍历元素
int key = A[start];//取第一个数作为分界值
//双指针去遍历
while(big<=end){
if(A[big]<key){
small++;
if(big!=small){
int temp = A[big];
A[big] = A[small];
A[small] = temp;
}
}
big++;
}
//最后需要交换分界值到指定位置
int temp = A[small];
A[small] = A[start];
A[start] = temp;
return small;//返回分界值的位置
}
}
该方法pivot元素仍然是选择数组段第一个元素作为分割点,但是交换方法上改为比较巧妙的双指针同向运动,一个small指针表示当前最后一个小段元素,big指针表示当前最后一个大段元素,即保证两个指针间的元素都为大段元素,最后只需要把pivot元素放在small指针后面即可(该方法的交换不是鱼pivot元素直接交换);
法三:代码如下
/**
* 快速排序
* */
public class QuickSort {
/**
* 主调用方法
* */
public void quickSort(int[] array,int n){
if(n>2){
this.recurSort(array, 0, n-1);
}
}
/**
* 递归进行不断划分,首先进行三个元素间的分割点选择,接着进行划分,并递归
* */
private void recurSort(int[] array,int start,int end){
if(start<end){
int pivot = (end-start)/2+start;
this.threeSort(array, start, pivot, end);
int curPivot = this.partition(array, pivot, start+1, end-1);
this.recurSort(array, curPivot+1, end);
this.recurSort(array, start, curPivot-1);
}
}
/**
* 对数组中三个位置进行排序,针对于快排中的选分割点,选择左中右三个元素中的中间元素作为分割点,防止最坏情况发生;
* 如果不采用该选择策略,该方法可以不用;
* */
private void threeSort(int[] array,int l,int m,int r){
if(array[l]>array[m]){
this.swap(array, l, m);
if(array[r]<=array[l]){
this.swap(array, r, l);
this.swap(array, m, r);
}else if(array[r]<=array[m])
this.swap(array, m, r);
}else{
if(array[r]<=array[l]){
this.swap(array, r, l);
this.swap(array, m, r);
}else if(array[r]<=array[m])
this.swap(array, m, r);
}
}
/**
* 交换两数组中两个元素位置
* @param i 位置1
* @param j 位置2
* */
private void swap(int[] array,int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
/**
* 一次快排的划分
* @param array 待排序数组
* @param pivot 划分点的序号
* @param start 数组中划分段的起始点序号
* @param end 数组中划分段的结束点序号
* */
private int partition(int[] array,int pivot,int start, int end){
int pivotIndex = pivot;
if(start<end){
/*记录结束点的序号*/
int toail = end;
/*记录划分点的值*/
int value = array[pivot];
/*把划分点与划分段最后一个数交换,即把划分点的值放到最后*/
this.swap(array, pivot, end);
end--;
/*主循环*/
while(start<end){
if(array[start]<value)
start++;
else if(array[end]>=value)
end--;
else{
this.swap(array, start, end);
}
}
/*循环出来后,做扫尾工作,准备还原分割点*/
/*当start的值大于分割点,则必须交换*/
if(array[start]>=value){
this.swap(array, start, toail);
pivotIndex = start;
}
/*如果start的值小于分割点的值,则交换start右边一个元素与分割点;如果右边没有元素,则保持不动*/
else if(array[start]<value && start+1<toail){
this.swap(array, start+1, toail);
pivotIndex = start+1;
}else{
pivotIndex = toail;
}
}
return pivotIndex;
}
}
该方法是通过把pivot元素一开始就放置到数组末端,然后双指针向中间夹逼,同时找到一大一小才互相交换元素,而不是与pivot元素交换;在选择pivot的方法上,当然允许继续选择数组段的第一个元素作为pivot元素,但是这里提供了一个threeSort()函数,该函数是将数组段的最左,中间,最右三个元素排序后返回中间元素的序号,将该元素作为pivot即可防止快排最坏情况的发送。