学习过程跟进《数据结构与算法分析》,主要代码大致与书中例程相同,若有疏漏或错误,请务必提醒我,我会尽力修正。
快速排序:在实践中最快的已知排序算法(但也有一些毛病)。
先放一下全代码:
//-----------快速排序-----------//
#define Cutoff 2//规定操作的左右范围长度
void Quicksort(int *Source,int N)//驱动例程
{ Qsort(Source, 0, N - 1); }
int Median3(int *A,int Left,int Right)
{
int Center = (Left + Right) / 2;
if (A[Left] > A[Center])
Swap(&A[Left], &A[Center]);
if (A[Left] > A[Right])
Swap(&A[Left], &A[Right]);
if (A[Center] > A[Right])
Swap(&A[Center],&A[Right]);
Swap(&A[Center], &A[Right - 1]);
return A[Right - 1];
}
//获取中位数(首位,中位,末位)
void Qsort(int *A,int Left,int Right)//实际例程
{
int i, j, Pivot;
if (Left + Cutoff <= Right)
{
Pivot = Median3(A, Left, Right);
i = Left; j = Right - 1;
for (;;)
{
while (A[++i] < Pivot){}
while (A[--j] > Pivot){}
if (i < j)
Swap(&A[i], &A[j]);
else
break;
}
Swap(&A[i], &A[Right - 1]);
Qsort(A, Left, i - 1);
Qsort(A, i + 1, Right);
}
else
InsertionSort(A + Left, Right - Left + 1);
}
void Qselect(int A[],int k,int Left,int Right)
{
int i, j, Pivot;
if (Left + Cutoff <= Right)
{
Pivot = Median3(A, Left, Right);
i = Left; j = Right - 1;
for (;;)
{
while (A[++i] < Pivot) {}
while (A[--j] > Pivot) {}
if (i < j)
Swap(&A[i], &A[j]);
else
break;
}
Swap(&A[i], &A[Right - 1]);
if(k<=i)
Qselect(A, k, Left, i - 1);
else if(k>i+1)
Qselect(A, k, i + 1, Right);
}
else
InsertionSort(A + Left, Right - Left + 1);//插入排序
}
int QuickSelect(int* Source, int k,int N)
{
Qselect(Source, k, 0, N - 1);
return Source[k-1];
}
快速排序的图解请移步其他大佬,这里主要是梳理一遍其排序过程,对一些晦涩的地方做出些许标记。
①首先从驱动例程进入。Left和Right分别标记为数组的左右端点。声明必要的常量。
(注:Cutoff常量能够决定“左右端点的间距”。之所以要这样做,是因为递归算法在大量数据的排序过程中虽然很方便,但从汇编的角度却不可避免的需要不停地入栈出栈,对于一些小规模数据的排序来说,这是一种浪费。所以当排序的数据量低于Cutoff的时候,选用另外一种更加简单的非递归算法要比原先的递归算法来得更加效率)
②Pivot取 首/中/末 位的中位数,并将i标记位Left(左端点),j标记为Right-1(最大索引数)
(注:Pivot的取值实际上是越接近全数组的中位数越好,因为这样可以尽可能的将数组拆分为同样大小的另外两个数组,从结论上来说,如果每一次都能等分,那么递归的次数是最少的,因此也是最快的。但在实际中,选取中位数是困难的一件事,所以只能大致取一个靠近中位数的来替代它。且还需要避免一些最糟糕的情况,比方说全数组的元素都相同,那么数组的分割将会毫无意义,又或者数组已经排好了序之类的。经过衡量,对于那些随机的投放元素的数组,这种取法能够尽可能的靠近中位数。)
(注:已经另外一个需要注意的是,Median3函数将选出的Pivot放在了A[Right-1]的位置,这样做能避免之后出现关键字与Pivot相同的时候进行的额外的操作)
③进入循环,直到 i 标记越过或是与 j 标记重叠。期间,在第一个while循环中,当 A[i]>Pivot的时候将会停下,同理的,j标记也是一样。最后将两个元素进行交换。
![](/qrcode.jpg)
④直到 i 标记与 j 标记重叠或越过后,将现在 i 标记所指的单元与Pivot交换。那么,现在 i 标记的左边的所有值都将小于Pivot,右边所有值都将大于Pivot。
⑤对 i 标记 左边的所有值进行同样的排序操作,结束后再对右边同理进行排序。
(注:最终必然会依靠插入排序对剩下的元素进行排序,请不要忘记这一点来观察图解)
快速选择:(以选出数组中第 k 大/小 的数为例)
从快速排序中派生出的算法。基本上同上面一样,但速度还能更快,因为它不需要对整个数组进行排序。
上述的第⑤步,只需要先判断我们选取的值是在Pivot左边还是Pivot的右边,然后排序所在的那一侧,就能顺利的选出目标。这样会减少很多操作,所有速度能更快。