//author: W.
//快速排序(利用分治法). 7.1,P85
//平均情况时间复杂度为O(nlgn),最坏情况为O(n^2)
//快速排序是就地排序,平均性能好,且常数因子很小,但最坏情况性能差,不过快排的平均情况与最佳情况运行时间很接近,如果输入是随机的,那么出现最坏情况的概率很小。
#include <stdio.h>
//为了让快排的性能更加接近平均性能,即降低最坏情况出现的概率,这里在PARTITION中选取x值时可以使用随机采样的方法
//随机采样x:利用一个随机函数产生出p~r中的一个随机值rand_index,用A[random_index]值作为x的值,这样即使输入是最坏情况,在排序中也会在很大的概率上为平均性能,见P90
//返回q的位置,使得A[p]~A[q-1] <= A[q] < A[q+1]~A[r]
int PARTITION(int A[], int p, int r)
{
int x = A[r];
int i = p-1;
int j;
int temp;
//循环不变式满足条件:A[p]~A[i] <= x < A[i+1]~A[j-1],即分成2个部分,一部分小于x,一部分大于x,且这二部分挨着。
//初始化:i == p-1, j == p, x == A[r], A[p]~A[i]无元素,A[i+1]~A[j-1]无元素,所以初始化成立
for(j = p; j < r; ++j)//保持:A[j]标识每次待判断的元素,每次判断A[j]与x的大小关系,1)A[j] > x则A[j]应该放入大于x的部分中,那么直接++j即可,即增大第二部分。
{ //2)A[j] <= x,则A[j]应该放入小于x的部分中,那么只需要将A[j]与A[i+1](大于x部分的第一项)交换位置,并++i即可,同时++j,即增大第一部分。
if(A[j] <= x)
{
temp = A[i+1];
A[i+1] = A[j];
A[j] = temp;
++i;
}
}
//终止:终止时j == r,此时A[p]~A[i] <= x < A[i+1]~A[r-1]
//下面把A[r]插入到正确位置,即A[i+1]处,即A[r]与A[i+1]交换
A[r] = A[i+1];
A[i+1] = x;
return i + 1; //返回得到的q的位置
}
//利用分治法进行快速排序
//分解:每次把一个数组A[p]~A[r]中选取一个q,并处理数组A,使得A[p]~A[q-1] <= A[q] <= A[q+1]~A[r]。
//解决:通过递归的方式对分出的子序列进行排序。其中当p >= r时,则该子数组中无元素或只有一个元素,则无需再分,即最小问题情况。
//合并:因为每次分解时,二个子部分都是就地排序的,所以无需再合并,而在分解时,得出的q也是正确的位置,所以也无需合并。即经过递归解决后,数组已排序.
void QUICKSORT(int A[], int p, int r)
{
if(p >= r)
{
return; //最小情况,已排序,直接返回。
}
int q = PARTITION(A, p, r);
QUICKSORT(A, p, q-1);
QUICKSORT(A, q+1, r);
}
void test_QuickSort()
{
int a[] = { 5, 2, 4, 6, 1, 3, 10, 6, 7, 9, 8, 123, 233, 3, 23, 33, 1134, 454353, 2, 0, 0, 0, 1};
int i;
for(i = 0; i < sizeof(a)/sizeof(int); ++i)
{
printf("%d ", a[i]);
}
printf("/n");
QUICKSORT(a, 0, sizeof(a)/sizeof(int)-1);
for(i = 0; i < sizeof(a)/sizeof(int); ++i)
{
printf("%d ", a[i]);
}
printf("/n");
}
int main(int argc, char** argv)
{
test_QuickSort();
return 0;
}
//输出:
//5 2 4 6 1 3 10 6 7 9 8 123 233 3 23 33 1134 454353 2 0 0 0 1
//0 0 0 1 1 2 2 3 3 4 5 6 6 7 8 9 10 23 33 123 233 1134 454353