Www p Themes c Quickwpthemes l Quickwpthemes r Quick C Szh % Www B%D Szh %Csearch% 7oitjht885 CE Themes % Www 4%
7www.moxiu.comE9 Quick 9search% 7oitjht885 6aE1% Themes 0searchA Szh %search5Asia%20Uncensored%20Authorship%20Seed%20%D1%C7%D6%DE%D4%AD%B4%B4%C7%F888a9wp-color-tag-cloud% Www 8
8search%searchA Themes % Szh 69 Quickwpthemes %search0% Szh 7快速排序详细分析
注:REF[n]为参考资料,列于文章结尾。
看了编程珠玑Programming Perls第11章关于快速排序的讨论,发现自己长年用库函数,已经忘了快排怎么写。于是整理下思路和资料,把至今所了解的快排的方方面面记录与此。
快速排序由C.A.R.Hoare于1962年提出,算法相当简单精炼,基本策略是随机分治。
首先选取一个枢纽元(pivot),然后将数据划分成左右两部分,左边的大于(或等于)枢纽元,右边的小于(或等于枢纽元),最后递归处理左右两部分。
分治算法一般分成三个部分:分解、解决以及合并。快排是就地排序,所以就不需要合并了。只需要划分(partition)和解决(递归)两个步骤。因为划分的结果决定递归的位置,所以Partition是整个算法的核心。
对数组S排序的形式化的描述如下(REF[1]):
快速排序最佳运行时间O(nlogn),最坏运行时间O(N^2),随机化以后期望运行时间O(nlogn),关于这些任何一本算法数据结构书上都有证明,就不写在这了,一下两点很重要:
所以诉时间复杂度的分析都是围绕枢纽元的位置展开讨论的。
void QuickSort(T A[], int p, int q)
{}
}
划分又分成两个步骤:选取枢纽元和按枢纽元将数组分成左右两部分
同样是为了方便,将选取枢纽元单独提出来成一个函数:select_pivot(T A[], int p, int q),该函数从A[p...q]中选取一个枢纽元并返回,且枢纽元放置在左端(A[p]的位置)。
int select_pivot(T A[], int p, int q)
{}
但是实际应用中,数据往往是部分有序的,如果仍用两端的元素最为枢纽元,则会产生很不好的划分,使算法退化成O(n^2)。所以要采用一些手段避免这种情况,我知道的有“随机选取法”和“三数取中法”。
int select_pivot_random(T A[], int p, int q)
{}
其中randInt(p, q)随机返回[p, q]中的一个数,C/C++里可由stdlib.h中的rand函数模拟。
int select_pivot_median3(T A[], int p, int q)
{}
虽然说分割方法只影响算法时间复杂度的系数,但是一个好系数也是比较重要的。这也就是为什么实际应用中宁愿选择可能退化成O(n^2)的快速排序,也不用稳定的堆排序(堆排序交换次数太多,导致系数很大)。
常见的分割方法有三种:
int partition(T A[], int p, int q)
{}
swap(A[p], A[j]);
return j;
}
双向扫描可以正常处理所有元素相同的情况,而且交换次数比单向扫描要少。
int partition(T A[], int p, int q)
{}
}
需要注意的是,返回值j并不是枢纽元的位置,但是仍然保证了A[p..j] <= A[j+1...q]。这种方法在效率上于双向扫描差别甚微,只是代码相对更为紧凑,并且用A[p]做哨兵元素减少了内层循环的一个if测试。
kWww Quickwpthemes Quick Wp Themes Szh Shared 7oitjht885 Quick Wp Themes LJ's Blogv i Quick Wp Themes Sites hWww Quickwpthemes Quick Wp Themes Szh Shared 7oitjht885 Quick Wp Themes LJ's Blogn o g Dating r Quick Wp Themes t t TIC+B5125+ A