目录 快速排序 (quick sort)是一种有效的排序算法。虽然算法在最坏的情况下运行时间为o(n^2),但由于平均运行时间为o(nlogn),并且在内存使用、程序实现复杂性上表现优秀,尤其是对快速排序 算法进行随机化的可能,使得快速排序 在一般情况下是最实用的排序方法之一。
快速排序 被认为是当前最优秀的内部排序方法。 快速排序 的实现基于分治法,具体分为三个步骤。假设待排序的序列为l[m..n]。
分解:序列l[m .. n]被划分成两个可能为空的子序列l[m .. pivot-1]和l[pivot+1 .. n],使l[m .. pivot-1]的每个元素均小于或等于l[pivot],同时l[pivot+1.. n]的每个元素均大于l[pivot]。其中l[pivot]称为这一趟分割中的主元(也称为枢轴、支点)。
解决:通过递归调用快速排序 ,对子序列l[m .. pivot-1]和l[pivot+1 .. r]排序。
合并:由于两个子序列是就地排序的,所以对它们的合并不需要操作,整个序列l[m .. n]已排好序。 内部排序
快速排序 是一种内部排序方法。也就是说快速排序 的排序对象是读入内存的数据。
比较排序
快速排序 确定元素位置的方法基于元素之间关键字大小的比较。
所有基于比较方法的排序方法的时间下界不会低于o(nlogn)。这个结论的具体证明,请参考有关算法的书籍,例如《算法导论》第8章。
快速排序 在理想情况下,能严格地达到o(nlogn)的下界。一般情况下,快速排序 与随机化快速排序 的平均情况性能都达到了o(nlogn)。
不稳定性
快速排序 是一种不稳定的排序方法。简单地说,元素a1, a2的关键字有a1.key=a2.key,则不稳定的排序方法不能保证a1, a2在排序后维持原来的位置先后关系。
原地排序
在排序的具体操作过程中,除去程序运行实现的空间消费(例如递归栈),快速排序 算法只需消耗确定数量的空间(即s(1),常数级空间)。
这个性质的意义,在于在内存空间受到限制的系统(例如mcu)中,快速排序 也能够很好地工作。 快速排序 每次将待排序数组分为两个部分,在理想状况下,每一次都将待排序数组划分成等长两个部分,则需要logn次划分。
而在最坏情况下,即数组已经有序或大致有序的情况下,每次划分只能减少一个元素,快速排序 将不幸退化为冒泡排序,所以快速排序 时间复杂度下界为o(nlogn),最坏情况为o(n^2)。在实际应用中,快速排序 的平均时间复杂度为o(nlogn)。
快速排序 在对序列的操作过程中只需花费常数级的空间。空间复杂度s(1)。
但需要注意递归栈上需要花费最少logn 最多n的空间。 快速排序 的最坏情况基于每次划分对主元的选择。基本的快速排序 选取第一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是o(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序 得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序 可以对于绝大多数输入数据达到o(nlogn)的期望时间复杂度。一位前辈做出了一个精辟的总结:“随机化快速排序 可以满足一个人一辈子的人品需求。”
随机化快速排序 的唯一缺点在于,一旦输入数据中有很多的相同数据,随机化的效果将直接减弱。对于极限情况,即对于n个相同的数排序,随机化快速排序 的时间复杂度将毫无疑问的降低到o(n^2)。 快速排序 的实现需要消耗递归栈的空间,而大多数情况下都会通过使用系统递归栈来完成递归求解。在元素数量较大时,对系统栈的频繁存取会影响到排序的效率。
一种常见的办法是设置一个阈值,在每次递归求解中,如果元素总数不足这个阈值,则放弃快速排序 ,调用一个简单的排序过程完成该子序列的排序。这样的方法减少了对系统递归栈的频繁存取,节省了时间的消费。
一般的经验表明,阈值取一个较小的值,排序算法采用选择、插入等紧凑、简洁的排序。一个可以参考的具体方案:阈值t=10,排序算法用选择排序。
阈值不要太大,否则省下的存取系统栈的时间,将会被简单排序算法较多的时间花费所抵消。
另一个可以参考的方法,是自行建栈模拟递归过程。但实际经验表明,收效明显不如设置阈值。 以下是一个用c++编写的快速排序 程序。虽然c标准库中提供了快速排序 ,但作为快速排序 的介绍,原理程序的代码更加有助于对快速排序 运行过程的分析。
在这个例程中,对于数组x[]的0~n-1号元素的排序,初始调用为:quicksort(x, 0, n-1);
int quicksort_partition(int l[], int lbb, int ubb)
{
//随机化
int irndpivid;
srand(unsigned(time(0)));
irndpivid = (rand() % (ubb - lbb + 1)) + lbb;
swap(l[irndpivid], l[ubb]);
//快排
int ipivvalue;
int i;
int ipivpos;
ipivvalue = l[ubb];
ipivpos = lbb - 1;
for (i=lbb; i<=ubb-1; i++)
{
if (l[ i ] <= ipivvalue)
{
ipivpos++;
swap(l[ipivpos], l[ i ]);
}
}
ipivpos++;
swap(l[ipivpos], l[ubb]);
return ipivpos;
}
void quicksort(int l[], int lbb, int ubb)
{
int ipiv;
if (lbb < ubb)
{
ipiv = quicksort_partition(l, lbb, ubb);
quicksort(l, lbb, ipiv - 1);
quicksort(l, ipiv + 1, ubb);
}
return;
} c++的标准库stdlib.h中提供了快速排序 函数。
请在使用前加入对stdlib.h的引用:#include <cstdlib> 或 #include <stdlib.h>
qsort(void* base, size_t num, size_t width, int(*)compare(const void* elem1, const void* elem2))
参数表
*base: 待排序的元素(数组,下标0起)。
num: 元素的数量。
width: 每个元素的内存空间大小(以字节为单位)。可用sizeof()测得。
int(*)compare: 指向一个比较函数。*elem1 *elem2: 指向待比较的数据。
比较函数的返回值
返回值是int类型,确定elem1与elem2的相对位置。
elem1在elem2右侧返回正数,elem1在elem2左侧返回负数。
控制返回值可以确定升序/降序。
一个升序排序的例程:
int compare(const void *elem1, const void *elem2)
{
return *((int *)(elem1)) - *((int *)(elem2));
}
int main()
{
int a[100];
qsort(a, 100, sizeof(int), compare);
return 0;
} 这个例程中假设待排序的数组是全局变量a[]。
procedure qsort(s,t:longint);
var
i,j,x,temp:longint;
begin
i:=s;j:=t;x:=a[(i+j)div 2];
repeat
while a[ i ]<x do inc(i); {找左边比他大的}
while a[j]>x do dec(j);{找右边比他小的}
if i<=j then{交换}
begin
temp:=a[ i ];a[ i ]:=a[j];a[j]:=temp;
inc(i);dec(j);
end;
until i>j;
if s<j then qsort(s,j);
if i<t then qsort(i,t);
end;