原文地址java
快速排序是C.R.A.Hoare提出的一种交换排序。它采用分治的策略,因此也称其为分治排序。算法
实现快速排序算法的关键在于,先在数组中选一个数做为基数,接着以基数为中心将数组中的数字分为两部分,比基数小的放在数组的左边,比基数大的放到数组的右边。接下来咱们能够用递归的思想分别对基数的左右两边进行排序。windows
整个快速排序能够总结为如下三步:数组
下面我用博客专家MoreWindows的挖坑填数+分治
思想来分析一个实例(使用快速排序将如下数组排序)性能
首先,取数组中的第一个数字52为第一次分区的基数ui
初始时,start=0、end=八、base=52(start区间的头、end区间的尾、base基数)spa
因为将array[0]中的数据赋给了基数base,咱们能够理解为在array[0]处挖了一个坑,固然这个坑是能够用其它数据来填充的。.net
接着咱们从end开始 向前找比base小或者等于base的数据,很快咱们就找了19,咱们就19这个数据填充到array[0]位置,这样array[8]位置就又造成了一个新的坑。不怕,咱们接着找符合条件的数据来填充这个新坑。 start=0、end=八、base=523d
而后咱们再从start开始向后找大于或等于base的数据,找到了88,咱们用88来填充array[8],这样array[1]位置也造成了一个新坑。 Don't Worry 咱们接着从end开始 向前找符合条件的数据来填充这个坑。 start=一、end=八、base=52code
须要注意的是start和end随着查找的过程是不断变化的。
以后一直重复上面的步骤,先从前向后找,再从后向前找。直至全部小于基数的数据都出如今左边,大于基数的数据都出如今右边。
最后将base中的数据填充到数组中便可完成此次分区的过程。
而后递归使左右区间重复分区过程,直至各个区间只有一个数,便可完成整个排序过程。
快速排序的核心是以基数为中心,将数组分为两个区间,小于基数的放到基数的左边,大于基数的放到基数的右边。
在第一次分区的时候咱们以数组中的第一个数据为基数,在array[0]处挖了一个坑,这时咱们只能从区间的后面往前找小于基数的数据来填充array[0]这个坑,若是直接从前日后找符合条件的数据,就会形成大于基数的数据留在了数组的左边。
因此咱们只能经过从后往前找小于基数的数据来填充前面的坑,从前日后找大于基数的数据来填充后面的坑。这样能够保证在只遍历一遍数组就能将数组分为两个区间
最后一步咱们用基数自己来填充数组的最后一个坑
代码实现:
// 分区
public static int partition(int[] array, int start, int end) {
int base = array[start];
while (start < end) {
while (start < end && array[end] >= base)
end --;
array[start] = array[end];
while (start < end && array[start] <= base)
start ++;
array[end] = array[start];
}
array[end] = base;
return start;
}
// 快速排序
public static void quickSort(int[] array,int start, int end) {
if(start < end) {
int index = partition(array, start, end);
quickSort(array, start, index-1);
quickSort(array, index+1, end);
}
}
复制代码
class QuickSort {
public:
//快速排序
int* quickSort(int* A, int n) {
QSort(A,0,n-1);
return A;
}
//对数组A[low,...,high]
void QSort(int *A,int low,int high) {
if(low<high) {
int n;
n=Partition(A,low,high);
QSort(A,low,n-1);
QSort(A,n+1,high);
}
}
//交换数组A[low,...,high]的记录,支点记录到位,并返回其所在位置,此时
//在它以前(后)的记录均不大(小)于它
int Partition(int *A,int low,int high) {
int key=A[low];
while(low<high) {
while(low<high&&A[high]>=key)
high--;
A[low]=A[high];
while(low<high&&A[low]<=key)
low++;
A[high]=A[low];
}
A[low]=key;
return low;
}
};
复制代码
当数据有序时,以第一个关键字为基准分为两个子序列,前一个子序列为空,此时执行效率最差。时间复杂度为O(n^2) 而当数据随机分布时,以第一个关键字为基准分为两个子序列,两个子序列的元素个数接近相等,此时执行效率最好。时间复杂度为O(nlogn) 因此,数据越随机分布时,快速排序性能越好;数据越接近有序,快速排序性能越差。
快速排序在每次“挖坑”的过程当中,须要 1 个空间存储基数。而快速排序的大概须要 NlogN次的处理,因此占用空间也是 NlogN 个。
下面咱们在来看几种改进的快排算法
快速排序中最重要的步骤就是将小于等于中轴元素的整数放到中轴元素的左边,将大于中轴元素的数据放到中轴元素的右边,这里咱们把该步骤定义为'切分'。以首元素做为中轴元素,下面介绍几种常见的'切分方式'。
这种方法就是咱们上面用到的'挖坑填数',在这再作个简单的总结。
**这种方法的基本思想是:**使用两个变量i和j,i指向最左边的元素,j指向最右边的元素,咱们将首元素做为中轴,将首元素复制到变量pivot中,这时咱们能够将首元素i所在的位置当作一个坑,咱们从j的位置从右向左扫描,找一个小于等于中轴的元素A[j],来填补A[i]这个坑,填补完成后,拿去填坑的元素所在的位置j又能够看作一个坑,这时咱们在以i的位置从前日后找一个大于中轴的元素来填补A[j]这个新的坑,如此往复,直到i和j相遇(i == j,此时i和j指向同一个坑)。最后咱们将中轴元素放到这个坑中。最后对左半数组和右半数组重复上述操做。
这种方法的代码实现请参考上面的完整代码。
**这种方法的基本思想是:**使用两个变量i和j,i指向首元素的元素下一个元素(最左边的首元素为中轴元素),j指向最后一个元素,咱们从前日后找,直到找到一个比中轴元素大的,而后从后往前找,直到找到一个比中轴元素小的,而后交换这两个元素,直到这两个变量交错(i > j)(注意不是相遇 i == j,由于相遇的元素还未和中轴元素比较)。最后对左半数组和右半数组重复上述操做。
public static void QuickSort1(int[] A, int L, int R){
if(L < R){//递归的边界条件,当 L == R时数组的元素个数为1个
int pivot = A[L];//最左边的元素做为中轴,L表示left, R表示right
int i = L+1, j = R;
//当i == j时,i和j同时指向的元素尚未与中轴元素判断,
//小于等于中轴元素,i++,大于中轴元素j--,
//当循环结束时,必定有i = j+1, 且i指向的元素大于中轴,j指向的元素小于等于中轴
while(i <= j){
while(i <= j && A[i] <= pivot){
i++;
}
while(i <= j && A[j] > pivot){
j--;
}
//当 i > j 时整个切分过程就应该中止了,不能进行交换操做
//这个能够改为 i < j, 这里 i 永远不会等于j, 由于有上述两个循环的做用
if(i <= j){
Swap(A, i, j);
i++;
j--;
}
}
//当循环结束时,j指向的元素是最后一个(从左边算起)小于等于中轴的元素
Swap(A, L, j);//将中轴元素和j所指的元素互换
QuickSort1(A, L, j-1);//递归左半部分
QuickSort1(A, j+1, R);//递归右半部分
}
}
复制代码
和前面两种方法同样,咱们选取最左边的元素做为中轴元素,A[1,i]表示小于等于pivot的部分,i指向中轴元素(i < 1),表示小于等于pivot的元素个数为0,j之后的都是未知元素(即不知道比pivot大,仍是比中轴元素小),j初始化指向第一个未知元素。
当A[j]大于pivot时,j继续向前,此时大于pivot的部分就增长一个元素
当A[j]小于等于pivot时,咱们注意i的位置,i的下一个就是大于pivot的元素,咱们将i增长1而后交换A[i]和A[j],交换后小于等于pivot的部分增长1,j增长1,继续扫描下一个。而i的下一个元素仍然大于pivot,又回到了先前的状态。
public static void QuickSort3(int[] A, int L, int R){
if(L < R){
int pivot = A[L];//最左边的元素做为中轴元素
//初始化时小于等于pivot的部分,元素个数为0
//大于pivot的部分,元素个数也为0
int i = L, j = L+1;
while(j <= R){
if(A[j] <= pivot){
i++;
Swap(A, i, j);
j++;//j继续向前,扫描下一个
}else{
j++;//大于pivot的元素增长一个
}
}
//A[i]及A[i]之前的都小于等于pivot
//循环结束后A[i+1]及它之后的都大于pivot
//因此交换A[L]和A[i],这样咱们就将中轴元素放到了适当的位置
Swap(A, L, i);
QuickSort3(A, L, i-1);
QuickSort3(A, i+1, R);
}
}
复制代码
三向切分快速排序的基本思想,用i,j,k三个将数组切分红四部分,a[0, i-1]表示小于pivot的部分,a[i, k-1]表示等于pivot的部分,a[j+1]以后的表示大于pivot的部分,而a[k, j]表示未进行比较的元素(即不知道比pivot大,仍是比pivot元素小)。咱们要注意a[i]始终位于等于pivot部分的第一个元素,a[i]的左边是小于pivot的部分。
咱们依旧选取最左边的元素做为中轴元素,初始化时,i = L,k = L+1,j=R(L表示最左边元素的索引,R表示最右边元素的索引)
经过上一段的表述可知,初始化时<pivot部分的元素个数为0,等于pivot部分元素的个数为1,大于pivot部分的元素个数为0,这显然符合目前咱们对所掌握的状况。k自左向右扫描直到k与j错过为止(k > j)。
这里咱们扫描的目的就是为了逐个减小未知元素,并将每一个元素按照和pivot的大小关系放到不一样的区间上去。
在k的扫描过程当中咱们能够对a[k]分为三种状况讨论
a[k] < pivot 交换a[i]和a[k],而后i和k都自增1,k继续扫描
a[k] = pivot k自增1,k接着继续扫描
a[k] > pivot 这个时候显然a[k]应该放到最右端,大于pivot的部分。可是咱们不能直接将a[k]与a[j]交换,由于目前a[j]和pivot的关系未知,因此咱们这个时候应该从j的位置自右向左扫描。而a[j]与pivot的关系能够继续分为三种状况讨论
注意,当扫描结束时,i和j的表示了=等于pivot部分的起始位置和结束位置。咱们只须要对小于pivot的部分以及大于pivot的部分重复上述操做便可。
public static void QuickSort3Way(int[] A, int L, int R){
if(L >= R){//递归终止条件,少于等于一个元素的数组已有序
return;
}
int i,j,k,pivot;
pivot = A[L]; //首元素做为中轴
i = L;
k = L+1;
j = R;
OUT_LOOP:
while(k <= j){
if(A[k] < pivot){
Swap(A, i, k);
i++;
k++;
}else
if(A[k] == pivot){
k++;
}else{// 遇到A[k]>pivot的状况,j从右向左扫描
while(A[j] > pivot){//A[j]>pivot的状况,j继续向左扫描
j--;
if(j < k){
break OUT_LOOP;
}
}
if(A[j] == pivot){//A[j]==pivot的状况
Swap(A, k, j);
k++;
j--;
}else{//A[j]<pivot的状况
Swap(A, i, j);
Swap(A, j, k);
i++;
k++;
j--;
}
}
}
//A[i, j] 等于 pivot 且位置固定,不须要参与排序
QuickSort3Way(A, L, i-1); // 对小于pivot的部分进行递归
QuickSort3Way(A, j+1, R); // 对大于pivot的部分进行递归
}
复制代码
双轴快速排序算法的思路和上面的三向切分快速排序算法的思路基本一致,双轴快速排序算法使用两个轴元素,一般选取最左边的元素做为pivot1和最右边的元素做pivot2。首先要比较这两个轴的大小,若是pivot1 > pivot2,则交换最左边的元素和最右边的元素,已保证pivot1 <= pivot2。双轴快速排序一样使用i,j,k三个变量将数组分红四部分
A[L+1, i]是小于pivot1的部分,A[i+1, k-1]是大于等于pivot1且小于等于pivot2的部分,A[j, R]是大于pivot2的部分,而A[k, j-1]是未知部分。和三向切分的快速排序算法同样,初始化i = L,k = L+1,j=R,k自左向右扫描直到k与j相交为止(k == j)。咱们扫描的目的就是逐个减小未知元素,并将每一个元素按照和pivot1和pivot2的大小关系放到不一样的区间上去。
在k的扫描过程当中咱们能够对a[k]分为三种状况讨论(注意咱们始终保持最左边和最右边的元素,即双轴,不发生交换)
a[k] < pivot1 i先自增,交换a[i]和a[k],k自增1,k接着继续扫描
a[k] >= pivot1 && a[k] <= pivot2 k自增1,k接着继续扫描
a[k] > pivot2: 这个时候显然a[k]应该放到最右端大于pivot2的部分。但此时,咱们不能直接将a[k]与j的下一个位置a[--j]交换(能够认为A[j]与pivot1和pivot2的大小关系在上一次j自右向左的扫描过程当中就已经肯定了,这样作主要是j首次扫描时避免pivot2参与其中),由于目前a[--j]和pivot1以及pivot2的关系未知,因此咱们这个时候应该从j的下一个位置(--j)自右向左扫描。而a[--j]与pivot1和pivot2的关系能够继续分为三种状况讨论
注意
public static void QuickSortDualPivot(int[] A, int L, int R){
if(L >= R){
return;
}
if(A[L] > A[R]){
Swap(A, L, R); //保证pivot1 <= pivot2
}
int pivot1 = A[L];
int pivot2 = A[R];
//若是这样初始化 i = L+1, k = L+1, j = R-1,也能够
//但代码中边界条件, i,j先增减,循环截止条件,递归区间的边界都要发生相应的改变
int i = L;
int k = L+1;
int j = R;
OUT_LOOP:
while(k < j){
if(A[k] < pivot1){
i++;//i先增长,首次运行pivot1就不会发生改变
Swap(A, i, k);
k++;
}else
if(A[k] >= pivot1 && A[k] <= pivot2){
k++;
}else{
while(A[--j] > pivot2){//j先增减,首次运行pivot2就不会发生改变
if(j <= k){//当k和j相遇
break OUT_LOOP;
}
}
if(A[j] >= pivot1 && A[j] <= pivot2){
Swap(A, k, j);
k++;
}else{
i++;
Swap(A, j, k);
Swap(A, i, k);
k++;
}
}
}
Swap(A, L, i);//将pivot1交换到适当位置
Swap(A, R, j);//将pivot2交换到适当位置
//一次双轴切分至少肯定两个元素的位置,这两个元素将整个数组区间分红三份
QuickSortDualPivot(A, L, i-1);
QuickSortDualPivot(A, i+1, j-1);
QuickSortDualPivot(A, j+1, R);
}
复制代码
下面代码初始化方式使得边界条件更加容易肯定,在下面代码的方式中A[L+1]~A[i-1]表示小于pivot1的部分,A[i]~A[j]表示两轴之间的部分,A[j]~A[R-1]表示大于pivot2的部分。
当排序数组中不存在pivot1~pivot2中的部分时,一趟交换完成后i刚好比j大1;当排序数组中仅仅存在一个元素x使得pivot1 <=x <=pivot2时,一趟交换完成,i和j刚好相等。
public static void QuickSortDualPivot(int[] A, int L, int R){
if(L >= R){
return;
}
if(A[L] > A[R]){
Swap(A, L, R); //保证pivot1 <= pivot2
}
int pivot1 = A[L];
int pivot2 = A[R];
int i = L+1;
int k = L+1;
int j = R-1;
OUT_LOOP:
while(k <= j){
if(A[k] < pivot1){
Swap(A, i, k);
k++;
i++;
}else
if(A[k] >= pivot1 && A[k] <= pivot2){
k++;
}else{
while(A[j] > pivot2){
j--;
if(j < k){//当k和j错过
break OUT_LOOP;
}
}
if(A[j] >= pivot1 && A[j] <= pivot2){
Swap(A, k, j);
k++;
j--;
}else{//A[j] < pivot1
Swap(A, j, k);//注意k不动
j--;
}
}
}
i--;
j++;
Swap(A, L, i);//将pivot1交换到适当位置
Swap(A, R, j);//将pivot2交换到适当位置
//一次双轴切分至少肯定两个元素的位置,这两个元素将整个数组区间分红三份
QuickSortDualPivot(A, L, i-1);
QuickSortDualPivot(A, i+1, j-1);
QuickSortDualPivot(A, j+1, R);
}
复制代码