基于快速排序的O(N)时间复杂度的TopK算法原理

一、背景

       实际应用中,我们经常面临这样的问题,即从一个序列S中选择其中最大的K个数(一般情况下K远小于|S|),我们将这种问题称为TopK问题。

       举一个例子,美剧《权利的游戏》中的每个人物,观众都会对其进行选择支持或不支持,这样每个人物都会都应一个热度值,这个值可以是支持者的数量,我们可以发现在腾讯视频中该剧中热度排名前3的人物加以了高亮标识,如下图:

基于快速排序的O(N)时间复杂度的TopK算法原理_第1张图片

    诸如此类的应用场景很多,本文借助快速排序的思想,给出解决此问题的一个时间复杂度为O(N)的算法。

二、快速排序思想

    为了引出本文基于快排TopK的思想,先给出快排的算法描述,令S为待排序集合,|S|表示集合元素数量。

    1、如果 S 中元素个数是0或1,则返回。

    2、取 S 中任一元素 v, 称之为枢纽元。

    3、将 S - {v} (S中其余元素) 分成两个不相交的集合:S_1 = \left \{ x \in S - \left \{ v \right \} | x\leqslant v\right \}S_2 = \left \{ x \in S - \left \{ v \right \} | x\geqslant v\right \}

    4、返回 {quicksort(S_1)后,继随 v, 继而quicksort(S_2) }。

    本人博客https://blog.csdn.net/gaoxueyi551/article/details/46778181中的代码便是该思想的实现,可供参考。

 

三、基于快速排序的TopK算法

     其实,只要稍加修改快排算法,即可实现平均时间复杂度为O(N)的TopK算法,我们称之为QuickSelect(S, K)。

     1、如果|S| = 1,返回S,否则若|S| < 20,则使用选择排序对S排序,选择最大的K个元素返回。(同快速排序步骤1)

     2、选取一个枢纽元 v 属于 S。(同快速排序2)

     3、将集合 S - {v} 分割成 S1 和 S2。 (同快速排序步骤3)

     4、如果K <= |S1| ,则K个元素必然全部位于集合S1中,并返回QuickSelect(S1, K);如果K = |S1| + 1,则集合S1 与 枢纽元 v 恰好是所求的K个数,我们将 S1 和 v 一并返回;如果K > |S1| + 1,那么S1和枢纽元v必然是K个元素的一部分,剩余K - |S1| - 1 个元素必然存在集合 S2 中,因此我们应该返回 S1 + v + QuickSelect(S2, K - |S1| - 1)。

     可以看到,该算法前3步和快速排序是相同的,唯一区别在于步骤4。仔细分析该算法可得如下结论:

  •   快速选择算法的递归调用次数是快速排序的一半。
  •   两种算法的最坏情形都是O(N*N),即集合已排序的情况。
  •   算法平均时间复杂度为O(N)。

     结论2易于理解。分析算法可知,每一轮递归开始之前,我们已经消去了1个集合,递归仅作用在另外一个集合,结论2于是成立。结论3的推导依赖结论1。

     在理想情况下,枢纽元 v 的选择使每轮递归都近似的将当前集合等分,故最多需要递归O(logN)次:

         第1次递归对应的集合长度为 |S / 2|,

         第2次递归对应的集合长度为 |S / 4|,

         第3次递归对应的集合长度为 |S / 8|,

         ......

         第log(N) 次递归对应的集合长度为 1,

     将O(logN)次递归的集合长度相加,

         Sum = |S / 2| + |S / 4| + |S / 8| + ...... + 1

     这是等比数列,根据等比数列求和方法可得Sum大约等于2N,故快速选择的平均时间复杂度为O(N)。

你可能感兴趣的:(数据结构与算法)