41 var pivot = rand_parttion(array,from,to) 42 quick_sort(array,from,pivot-1); 43 quick_sort(array,pivot+1,to); 44 } 45 } 46 array ={2;46;5;17;1;2;3;99;12;56;66;21}; 47 quick_sort(array,1,#array) 48 //输出结果 49 for(i=1;#array;1){ 50 io.print( array[i] ) 51 } 52 execute(\按任意键继续 53 io.close();//关闭控制台 54 Balanced Quicksort(平衡快速排序) 55 int *quicksort(int *pData,int left,int right) //此乃快速排序的变种 56 { //平衡快速排序Balanced quicksort 57 int i,j; //适用于出现近似顺序数据 58 int temp; //或逆序数据的概率较大的数据集合 59 int middle; 60 i=left; 61 j=right; 62 middle=pData[(left+right)/2]; 63 do{ 64 while(pData[i] 编辑本段性能分析 下面我们就最好情况,最坏情况和平均情况对快速排序算法的性能作一点分析。 注意:这里为方便起见,我们假设算法Quick_Sort的范围阈值为1(即一直将线性表分解到 只剩一个元素),这对该算法复杂性的分析没有本质的影响。 我们先分析函数partition的性能,该函数对于确定的输入复杂性是确定的。观察该函数,我们发现,对于有n个元素的确定输入L[p..r],该函数运行时间显然为θ(n)。 最坏情况 无论适用哪一种方法来选择pivot,由于我们不知道各个元素间的相对大小关系(若知道就已经排好序了),所以我们无法确定pivot的选择对划分造成的影响。因此对各种pivot选择法而言,最坏情况和最好情况都是相同的。 我们从直觉上可以判断出最坏情况发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候(设输入的表有n个元素)。下面我们暂时认为该猜测正确,在后文我们再详细证明该猜测。 对于有n个元素的表L[p..r],由于函数Partition的计算时间为θ(n),所以快速排序在序坏情况下的复杂性有递归式如下: T(1)=θ(1),T(n)=T(n-1)+T(1)+θ(n) (1) 用迭代法可以解出上式的解为T(n)=θ(n2)。 这个最坏情况运行时间与插入排序是一样的。 下面我们来证明这种每次划分过程产生的两个区间分别包含n-1个元素和1个元素的情况就是最坏情况。 设T(n)是过程Quick_Sort作用于规模为n的输入上的最坏情况的时间,则 T(n)=max(T(q)+T(n-q))+θ(n),其中1≤q≤n-1 (2) 我们假设对于任何k T(n)≤max(cq2+c(n-q)2)+θ(n)=c*max(q2+(n-q)2)+θ(n) 因为在[1,n-1]上q2+(n-q)2关于q递减,所以当q=1时q2+(n-q)2有最大值n2-2(n-1)。于是有: T(n)≤cn2-2c(n-1)+θ(n)≤cn2 只要c足够大,上面的第二个小于等于号就可以成立。于是对于所有的n都有T(n)≤cn。 这样,排序算法的最坏情况运行时间为θ(n2),且最坏情况发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候。 时间复杂度为o(n2)。 最好情况 如果每次划分过程产生的区间大小都为n/2,则快速排序法运行就快得多了。这时有: T(n)=2T(n/2)+θ(n),T(1)=θ(1) (3) 解得:T(n)=θ(nlogn) 快速排序法最佳情况下执行过程的递归树如下图所示,图中lgn表示以2位底的对数,而本文中用logn表示以2位底的对数. 图2快速排序法最佳情况下执行过程的递归树 由于快速排序法也是基于比较的排序法,其运行时间为Ω(nlogn),所以如果每次划分过程产生的区间大小都为n/2,则运行时间θ(nlogn)就是最好情况运行时间。 但是,是否一定要每次平均划分才能达到最好情况呢?要理解这一点就必须理解对称性是如何在描述运行时间的递归式中反映的。我们假设每次划分过程都产生9:1的划分,乍一看该划分很不对称。我们可以得到递归式: T(n)=T(n/10)+T(9n/10)+θ(n),T(1)=θ(1) (4) 这个递归式对应的递归树如下图所示: 图3(4)式对应的递归树 请注意该树的每一层都有代价n,直到在深度log10n=θ(logn)处达到边界条件,以后各层代价至多为n。递归于深度log10/9n=θ(logn)处结束。这样,快速排序的总时间代价为T(n)=θ (nlogn),从渐进意义上看就和划分是在中间进行的一样。事实上,即使是99:1的划分时间代价也为θ(nlogn)。其原因在于,任何一种按常数比例进行划分所产生的递归树的深度都为θ(nlogn),其中每一层的代价为O(n),因而不管常数比例是什么,总的运行时间都为θ(nlogn),只不过其中隐含的常数因子有所不同。(关于算法复杂性的渐进阶,请参阅算法的复杂性) 平均情况 我们首先对平均情况下的性能作直觉上的分析。 要想对快速排序的平均情况有个较为清楚的概念,我们就要对遇到的各种输入作个假设。通常都假设输入数据的所有排列都是等可能的。后文中我们要讨论这个假设。 当我们对一个随机的输入数组应用快速排序时,要想在每一层上都有同样的划分是不太可能的。我们所能期望的是某些划分较对称,另一些则很不对称。事实上,我们可以证明,如果选择L[p..r]的第一个元素作为支点元素,Partition所产生的划分80%以上都比9:1更对称,而另20%则比9:1差,这里证明从略。 平均情况下,Partition产生的划分中既有“好的”,又有“差的”。这时,与Partition执行过程对应的递归树中,好、差划分是随机地分布在树的各层上的。为与我们的直觉相一致,假设好、差划分交替出现在树的各层上,且好的划分是最佳情况划分,而差的划分是最坏情况下的划分,图4(a)表示了递归树的连续两层上的划分情况。在根节点处,划分的代价为n,划分出来的两个子表的大小为n-1和1,即最坏情况。在根的下一层,大小为n-1的子表按最佳情况划分成大小各为(n-1)/2的两个子表。这儿我们假设含1个元素的子表的边界条件代价为1。 (a) (b) 图4 快速排序的递归树划分中的两种情况 在一个差的划分后接一个好的划分后,产生出三个子表,大小各为1,(n-1)/2和(n-1)/2,代价共为2n-1=θ(n)。这与图4(b)中的情况差不多。该图中一层划分就产生出大小为(n-1)/2+1和(n-1)/2的两个子表,代价为n=θ(n)。这种划分差不多是完全对称的,比9:1的划分要好。从直觉上看,差的划分的代价θ(n)可被吸收到好的划分的代价θ(n)中去,结果是一个好的划分。这样,当好、差划分交替分布划分都是好的一样:仍是θ(nlogn),但θ记号中隐含的常数因子要略大一些。关于平均情况的严格分析将在后文给出。 在前文从直觉上探讨快速排序的平均性态过程中,我们已假定输入数据的所有排列都是等可能的。如果输入的分布满足这个假设时,快速排序是对足够大的输入的理想选择。但在实际应用中,这个假设就不会总是成立。 解决的方法是,利用随机化策略,能够克服分布的等可能性假设所带来的问题。 一种随机化策略是:与对输入的分布作“假设”不同的是对输入的分布作“规定”。具体地说,在排序输入的线性表前,对其元素加以随机排列,以强制的方法使每种排列满足等可能性。事实上,我们可以找到一个能在O(n)时间内对含n个元素的数组加以随机排列的算法。这种修改不改变算法的最坏情况运行时间,但它却使得运行时间能够独立于输入数据已排序的情况。 另一种随机化策略是:利用前文介绍的选择支点元素pivot的第四种方法,即随机地在L[p..r]中选择一个元素作为支点元素pivot。实际应用中通常采用这种方法。 快速排序的随机化版本有一个和其他随机化算法一样的有趣性质:没有一个特别的输入会导致最坏情况性态。这种算法的最坏情况性态是由随机数产生器决定的。你即使有意给出一个坏的输入也没用,因为随机化排列会使得输入数据的次序对算法不产生影响。只有在随机数产生器给出了一个很不巧的排列时,随机化算法的最坏情况性态才会出现。事实上可以证明几乎所有的排列都可使快速排序接近平均情况性态,只有非常少的几个排列才会导致算法的 近最坏情况性态。 一般来说,当一个算法可按多条路子做下去,但又很难决定哪一条保证是好的选择时,随机化策略是很有用的。如果大部分选择都是好的,则随机地选一个就行了。通常,一个算法在其执行过程中要做很多选择。如果一个好的选择的获益大于坏的选择的代价,那么随机地做一个选择就能得到一个很有效的算法。我们在前文已经了解到,对快速排序来说,一组好坏相杂的划分仍能产生很好的运行时间。因此我们可以认为该算法的随机化版本也能具有较好的性态。 在前文我们从直觉上分析了快速排序在平均情况下的性能为θ(nlogn),我们将在下面定量地分析快速排序法在平均情况下的性能。为了满足输入的数据的所有排列都是等可能的这个假设,我们采用上面提到的随机选择pivot的方法,并且在Select_pivot函数中将选出的pivot与L[p]交换位置(这不是必需的,纯粹是为了下文分析的方便,这样L[p]就是支点元素pivot)。那种基于对输入数据加以随机排列的随机化算法的平均性态也很好,只是比这儿介绍的这个版本更难以分析。 我们先来看看Partition的执行过程。为简化分析,假设所有输入数据都是不同的。即使这个假设不满足,快速排序的平均情况运行时间仍为θ(nlogn),但这时的分析就要复杂一些。 由Partition返回的值q仅依赖于pivot在L[p..r]中的秩(rank),某个数在一个集合中的秩是指该集合中小于或等于该数的元素的个数。如果设n为L[p..r]的元素个数,将L[p]与L[p..r]中的一个随机元素pivot交换就得rank(pivot)=i(i=1,2,..,n)的概率为l/n。 下一步来计算划分过程不同结果的可能性。如果rank(pivot)=1,即pivot是L[p..r]中最小的元素,则Partition的循环结束时指针i停在i=p处,指针j停在k=p处。当返回q时,划分结果的\低区\中就含有唯一的元素L[p]=pivot。这个事件发生的概率为1/n,因为rank(pivot)=i的概率为1/n。 如果rank(pivot)≥2,则至少有一个元素小于L[p],故在外循环while循环的第一次执行中,指针i停于i=p处,指针j则在达到p之前就停住了。这时通过交换就可将L[p]置于划分结果的高区中。当Partition结束时,低区的rank(pivot)-1个元素中的每一个都严格小于pivot(因为假设输入的元素不重复)。这样,对每个i=1,2,..,n-1,当rank(pivot)≥2时,划分的低区中含i个元素的概率为 l/n。 把这两种情况综合起来,我们的结论为:划分的低区的大小为1的概率为2/n,低区大小为i的概率为1/n,i=2,3,..n-1。 现在让我们来对Quick_Sort的期望运行时间建立一个递归式。设T(n)表示排序含n个元素的表所需的平均时间,则: (5) 其中T(1)=θ(1)。 q的分布基本上是均匀的,但是q=1的可能性是其他值的两倍。根据前面作的最坏情况的分析有: T(1)=θ(1),T(n-1)=θ(n2),所以 这可被(5)式中的θ(n)所吸收,所以(5)式可简化为: (6) 注意对k=1,2,..,n-1,和式中每一项T(k)为T(q)和T(n-q)的机会各有一次,把这两项迭起来有: (7) 我们用代入法来解上述递归方程。归纳假设T(n)≤a*nlogn+b,其中a>0,b>0为待定常数。可以选择足够大的a,b使anlogn+b>T(1),对于n>1有: (8) 下面我们来确定和式 (9) 的界。 因为和式中每一项至多是nlogn,则有界: 这是个比较紧的界,但是对于解递归式(8)来说还不够强。为解该递归式,我们希望有界: 为了得到这个界,可以将和式(9)分解为两部分,这时有: 等号右边的第一个和式中的logk可由log(n/2)=logn-1从上方限界。第二个和式中的logk可由logn从上方限界,这样, 对于n≥2成立。即: (10) 将(10)代入(8)式得: (11) 因为我们可以选择足够大的a使a*n/4能够决定θ(n)+b,所以快速排序的平均运行时间为θ(nlogn)。 (12) 随机快排推荐程序 pascal 语言 1): 1 var 2 a:array[1..100000]of longint; 3 i,n:longint; 4 procedure paixu(head,tail:longint); 5 var i,j,x,p:longint; 6 begin 7 if head>=tail then exit; 8 p:=random(tail-head+1)+head; 9 x:=a[p];a[p]:=a[head]; 10 i:=head;j:=tail; 11 while i<>j do 12 begin 13 while(i<>j)and(a[j]>x) do dec(j); 14 if i<>j then begin a[i]:=a[j];inc(i);end; 15 while(i<>j)and(a[i]
快速法排序(6)
2019-01-27 18:24
快速法排序(6).doc
将本文的Word文档下载到电脑
下载失败或者文档不完整,请联系客服人员解决!