快速选择算法(基于快排)

快排给了我们一种快速选择数组第k小的思路:
        利用一个数组中的主元,消耗O(n)的时间将数组分为两部分(利用Qsort的Part函数):前半部分的值都小于等于主元,后半部分的值都大于主元,这样若前半部分元素的个数大于等于k,那么第k小的元素一定在前半部分,对前半部分递归调用即可;反之,则在后半部分,设前半部分有t个元素,那么第k小一定是后半部分中第kt小,递归调用即可,直到我们的区间中只有一个元素,结束递归,这玩意就是我们所需要的第k小.
这样我们消耗的时间为T(n)=T(k)+cn,其中,k为我们所进入的那半部分元素的个数,当然,由于众所周知的快排不稳定性,即使是快速选择也有可能给我们玩出O(n2)的复杂度出来,我们的算法是很容易被卡的,如果加入随机化,即随机化地选取主元,我们就有令人安心的复杂度:O(n),设对k<n都有E[T(k)]pk,那么对k=n,我们有

E[T(n)]=E[T(k)]+cni=1n1E[T(max{ni,i})]n1+cn

E[T(n)]p3n24(n1)+cn<p3n2+n44(n1)+cnpnp>4cnn4
当然,n时,p4c因此,我们的时间复杂度可以为T(n)=4cn是线性的.
但我们总是不满意,一个运行时间期望为O(n)的算法可以运行O(n2)之久,我们想要一个最坏O(n)的算法,这是有的:
我们考虑使快速选择算法运行时间达到O(n2)的原因:主元的选择.
这样,我们考虑主动为Part函数选择一个合适的主元:它应该稳定在(nk,nt)之间,这样我们就避免了极端情况的发生,从而使我们的快速选择能稳定下来,
我们考虑这样的启发式策略:将数组分为nc个相等的部分,每个部分均有c个元素(最后一个部分可能有不到c个元素,但我们同样可以处理,同时也不影响复杂度,我这里为节约字数(挺累的),就不再考虑了).我们将nc个小部分简单排序,取每个小部分的中位数,再在所取的中位数中找到他们的中位数,这个中位数x就是我们合适的主元.
我们来想一想,在我们得到的nc个中位数中,x的前面有n2c个数,而这n2c个数是原来几个小部分的中位数,每个中位数的前面都有c2个数,故而我们所得到的x最小为第c2n2c个数,类似地,x的后面最少也有c2n2c个数(这里并不精确地给出值,而只是相差几个小常数,这无关紧要,我们只要n的系数).
这样我们就稳定地给出了一个合适的主元x,它可以稳定地将数组划分为两个差别不是特别大的部分,通过对合适的部分进行递归调用,我们就可以快速求出第k小;
下面,我们来求合适的c,并证明选取合适的c可以使我们在O(n)时间内完成计算:
首先,我们总是碰到了最坏情况:
T(n)=T(nc)+T(nc2n2c)+q.c2.nc

首先:我们要保证
nc+nc2n2c<n

否则我们就会得到O(nlogn)的时间复杂度,这不是我们所期望的;
解上述不等式,解得c>4
k<n我们有T(k)pk成立,那么对n我们有:
T(n)pnc+p(nc2n2c)+qcnpn

我们得到
pqc2c22

最后,我们给p取最优值得到c=5:p50q;c=7:p49q
我们取c=5,c=7都是合适的,都可以使我们的快速选择算法稳定于O(n),
从而,我们得到了稳定的快速选择算法;(好累啊,休息一下).

展开阅读全文
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值