一、问题描述
假设数列 B={} 是由 n 个不重复的数字按从小到大有序排列得到的,有序数列 B 是未知的,仅方便后续分析描述,数列 A={} 由数列 B 随机重排列得到,现使用小顶堆算法求数列 A 的第 k 个最大的数,求算法的平均时间复杂度。

二、算法分析
算法总体分为两个阶段:“堆初始化”与“堆更新”,依次对两个阶段进行分析:

阶段1:选取数列 A 的前 k 个数字进行堆的初始化,设该堆的初始最小值为 ,可知 M 有 n-k+1 种可能,分别为 1、2、3 …… n-k+1,现计算每种可能出现的概率 P{M=m}:
易知从 n 个数字中随机选取 k 个数字,共有 种可能;
大于 的数字共 n-m 个,则随机选取 k 个数字,最小值恰好为 的情况共 种;
每种选取结果均等可能出现,满足古典概型,则有:

阶段2:完成堆的初始化后,需要逐个将数列 A 剩余的 n-k 个元素和堆顶元素 进行比较,如果数列元素大于堆顶元素,则将之插入堆中进行更新,否则忽略该元素。可知算法的效率取决于更新操作发生的次数 C,而更新操作发生的次数又取决于比 大的元素的排列情况(设该排列为 G={},因为其中 k-1 个在初始化阶段就已经处于堆中,所以更新阶段比 大的只有 n-m-k+1 个),在 G 的所有可能排列下,堆的更新次数的期望为调和级数的值,证明如下:

设数列 G 由 t 个不重复元素随机排列组成,易知 G 共有 种可能,其触发堆更新的次数C的期望设为 ,在所有可能的排列中,能够触发s次堆更新的排列设有 种,所有排列触发更新的次数总和设为 ,有:


则随机数列 G 触发堆更新的期望可以表示为:

现在尝试建立从 的递推关系式,从而求解出表达式:可以想到,t个元素组成的数列是由 t-1 个元素的数列插入一个新元素得到的,不妨设该元素为值最小的元素 (因为我们对数列内容没有额外限制,所以这种假设是合理的,实际上这个元素可以是任意一个元素,而取最小值则有利于分析),下面讨论插入的情况:
将其插入到原数列的第0号位置(数列头部),此时新数列触发次数增加了1,因为该元素不会“屏蔽”后面的元素,也没有元素会去“屏蔽”它, 在0号位置的所有排列触发次数总和为
将其插入到原数列的第 i 号位置(非数列头部,一共有 t-1 种可能),此时新数列触发次数与原数列相同,因为该元素不会“屏蔽”后面的元素,但是前面的元素会去“屏蔽”它(比它大), 在第 i 号位置的所有排列触发次数总和为
那么我们就能得到:


即:


将该结论应用到Top K问题中(特殊地,当 m=n-k+1 时更新次数为0),有:

那么,对于由同一有序数列 B 经过随机排列得到的所有不同数列 A,运用小顶堆算法求解 Top K 问题时,堆的平均更新次数 C 为:

考虑到调和级数没有通项公式,而组合数有不少可以化简的技巧,所以尝试调换内外求和的顺序,将上述二重求和表达式展开,得到:

自变量 公共项 i=1 i=2 i=3 ··· i=n-k-1 i=n-k
m=n-k 1 ...
m=n-k-1 1 ...
m=n-k-2 1 ...
... ... ... ... ... ... ... ...
m=2 1 ...
m=1 1 ...

调换求和次序,将 m 作为内层求和变量,i 作为外层求和变量,得到:

注意到(对下式进行展开,并利用凑项技巧,使得组合数的加法性质得以应用):

那么:

又注意到(将下列组合数展开为阶乘形式可得等式):

那么:

借助同样的凑项化简组合数技巧,得:

其中 的值小于1,可以忽略不计,而调和级数有近似公式,n 越大时越精确:

故:

上述表达式是堆更新操作发生的次数期望,则堆不发生更新的期望便为:,而堆进行一次更新的复杂度为 ,不更新的复杂度为 1,堆初始化的复杂度为 k,故算法整体的复杂度为:



========== 全 === 文 === 完 ==========