005【算法篇】随机化快速排序及其时间复杂度

呃,本文有点长……还用到一点点概率论知识

在讲随机化之前,先说下目前大家所熟识的快速排序,先上伪代码:

PARTITION(A,p,r)
  x = A[p]
  i=p
  for j=p+1 to r
    if A[j]<=x
        i=i+1
        A[i]<->A[j]
  A[i]<->A[p]
  return i

最坏情况下的时间复杂度

我们先来分析下最坏情况下的时间复杂度。何为最坏情况?输入的数据已升序或者降序,致使每次划分的时候总有一个子数组中的元素个数为0,而另一个子数组中的元素个数为n-1,由此我们知道此时的时间复杂度为:
T(n)=T(0)+T(n-1)+(n)\tag{1}
我们根据上一讲的递归树法进行分析,如下图所示:

WechatIMG46.png

故(1)式的时间复杂度计算如下:
\begin{align} T(n)&=T(0)+T(n-1)+(n)\\ &=T(0)+T(n-1)+cn\\ &=(\sum^n_{k=1}ck)\\ &=(n^2) \end{align}

最好情况下的时间复杂度

说完最坏情况,我们再聊最好情况,虽然我们一般不分析这种bogus。最好的情况下是什么样的呢?一般是说每次划分的主元刚好把数组一分为二,两个子数组的元素个数均为n/2,由此其时间复杂度为:
\begin{align} T(n)&=2T(n/2)+(n)\\ &=(nlgn) \end{align}
具体过程不证明了,具体可参见上一讲的内容。

综合来说,目前的快速排序其时间复杂度介于上述两种情况之间,也即:
T(n)=(n^2)=(nlgn)\tag{2}

由于输入的数据其排序程度我们难以控制,再加上实际工作中大部分情况下输入数据其实已经大致排序,所以使用常规的快速排序将趋于最坏情况下的时间复杂度,这也是我们不想看到的结果,由此下文我们将开始讨论随机化的快速排序。


改变主元位置

由于我们无法控制输入数据的排序程度,又想得到趋近于最好情况的时间复杂度,我们还有什么办法?

既然我们无法控制输入数据,那我们是不是可以尝试下控制每次划分的主元?由于常规的快速排序我们的主元要么在数组的队首或者队尾,如果我们换到其它位置,可不可行?比如针对一组随机输入数据,每次按1:9来划分数组,会有怎么样的时间复杂度?

一般我们的思路是先大胆假设,然后小心论证,如果论证结果确实符合预期,那我们就可以对代码进行改造。话不多说,按刚刚的1:9来划分数组,我们将得到这样一个时间复杂度:
T(n)=T(\frac{1}{10}n)+T(\frac{9}{10}n)+(n)\tag{3}
这种情况下无法直接使用主定理法,所以我们还是用老朋友递归树法画图如下:

WechatIMG48.png

由此可知:
\begin{align} T(n)&=T(\frac{1}{10}n)+T(\frac{9}{10}n)+(n)\\ &=(\sum^{log_{10}n \to log_\frac{10}{9}n}_{k=1}ck)\\ &=(nlog_\frac{10}{9}n)\\ &\leq (nlgn) \end{align}
发现了吗?最坏情况下的时间复杂度开始趋近于(nlgn)了!由此可以证明,针对随机化的输入数据,改变主元位置能够显著影响时间复杂度。

随机化快速排序

借由上文我们已经很清楚改变主元位置对时间复杂度的影响,那么我们是不是固定一个固定比例的划分就可以了?答案不是的,因为输入的数据存在一定随机性,我们不能保证每次用户提供的数据都是随机排序的,那么第一次按固定比例的划分假设是平衡的,不保证以后每次递归后的划分是平衡的,由此为了在各种输入场景下均保持良好的时间复杂度,我们想到了随机主元位置。

所谓随机主元位置,也即每次划分时的位置随机性选择A[1..n]中的某个位置k,将数组随机机划分为A[1..k-1]和A[k+1..n]两部分。

由于主元位置是随机的,也即每次主元位置的概率是相等的,数理统计中称x~B(0,1),也即0-1分布,由此主元刚好划分到k时的数学期望为:
E[X_k]=\frac{1}{n}\tag{4}
当主元位置随机之后,时间复杂度如何求解?如何验证确实针对任何输入数据情况下,均能有较好的时间复杂度?

当主元位置介于[1..n]之间时,共有n种划分方式,由此我们可以写出随机化下的时间复杂度:
\begin{align} T(n)&= \begin{cases} T(0)+T(n-1)+(n),\quad当k=1\\ T(1)+T(n-2)+(n),\quad当k=2\\ ...\\ T(n-1)+T(0)+(n),\quad当k=n \end{cases}\\\\ &=\sum^n_{k=1}X_k[T(k)+T(n-k-1)+(n)] \end{align}\tag{5}

由于k的位置是随机的,所以时间复杂度也是随机的,加上随机的机率相同,那么我们只需要利用概率论中的数学期望进行求解即可。

我们对(5)式进一步利用数学期望进行求解,可得如下过程:
\begin{align} E[T(n)]&=E[\sum^n_{k=1}X_k[T(k)+T(n-k-1)+(n)]]\\ &=\sum^n_{k=1}E[X_k[T(k)+T(n-k-1)+(n)]]\\ &=\sum^n_{k=1}E[X_k]*E[T(k)+T(n-k-1)+(n)]\\ &=\frac{1}{n}\sum^n_{k=1}E[T(k)+T(n-k-1)+(n)]\\ &=\frac{2}{n}\sum^n_{k=1}E[T(k)]+(n)\\ &...\\\\ &\leq cnlgn,\quad当c>0\tag{6} \end{align}
式(6)最后一步的过程比较艰辛,我不是数学专业的,所以也搞不清楚计算过程,反正就这个么答案……感兴趣的童鞋可以利用上一讲的代换法进行验证(可能要用到微积分,我就不写了)。

由此可知,针对任何输入数组,无论其排序程度如何,随机化的快速排序的时间复杂度为(cnlgn),趋近于最好情况下的时间复杂度,据说至少三倍速度于常规的快速排序。

最后奉上随机化快速排序的伪代码如下:

RANDOMIZED_PARTITION(A,p,r)
    i=random(p,r)
    A[p]<->A[i]
    return PARTITION(A,p,r)
    
RANDOMIZED_QUICKSORT(A,p,r)
    if p

你可能感兴趣的:(005【算法篇】随机化快速排序及其时间复杂度)