什么会导致算法具有O(log log n)复杂度?

时间:2013-05-09 22:13:55

标签: algorithm big-o time-complexity complexity-theory logarithm

This earlier question解决了可能导致算法具有O(log n)复杂性的一些因素。

什么会导致算法具有时间复杂度O(log log n)?

2 个答案:

答案 0 :(得分:188)

O(log log n)术语可以显示在各种不同的位置,但通常会有两个主要路径到达此运行时。

按平方根收缩

正如在链接问题的答案中所提到的,算法具有时间复杂度O(log n)的常用方法是通过在每次迭代时将输入的大小反复减小某个常数因子来使该算法起作用。如果是这种情况,算法必须在O(log n)次迭代后终止,因为在通过常量进行O(log n)除法之后,算法必须将问题大小缩小到0或1.这就是为什么,例如,二进制搜索具有复杂度O(log n)。

有趣的是,有一种类似的方法可以缩小产生O形式(log log n)运行时的问题的大小。不是在每一层将输入分成两半,如果我们在每一层采用大小的平方根,会发生什么?

例如,我们取数字65,536。在我们降到1之前,我们有多少次将其除以2?如果我们这样做,我们得到

  • 65,536/2 = 32,768
  • 32,768/2 = 16,384
  • 16,384 / 2 = 8,192
  • 8,192 / 2 = 4,096
  • 4,096/2 = 2,048
  • 2,048/2 = 1,024
  • 1,024/2 = 512
  • 512/2 = 256
  • 256/2 = 128
  • 128/2 = 64
  • 64/2 = 32
  • 32/2 = 16
  • 16/2 = 8
  • 8/2 = 4
  • 4/2 = 2
  • 2/2 = 1

这个过程需要16个步骤,而且65,536 = 2 16 的情况也是如此。

但是,如果我们在每个级别取平方根,我们得到

  • √65,536= 256
  • √256= 16
  • √16= 4
  • √4= 2

请注意,它只需要四个步骤就可以一直降到2.为什么会这样?好吧,让我们用2的幂来重写这个序列:

  • √65,536=√2 16 =(2 16 1/2 = 2 8 = 256
  • √256=√2 8 =(2 8 1/2 = 2 4 = 16
  • √16=√2 4 =(2 4 1/2 = 2 2 = 4
  • √4=√2 2 =(2 2 1/2 = 2 1 = 2

请注意,我们按照序列2 16 →2 8 →2 4 →2 2 →2 1 。在每次迭代中,我们将二次幂的指数削减。这很有意思,因为这可以回到我们已经知道的东西 - 你只能将数字k分成一半O(log k)次,然后再降到零。

所以取任意数n并将其写为n = 2 k 。每次取n的平方根时,将该等式中的指数减半。因此,在k降至1或更低之前,只能应用O(log k)平方根(在这种情况下,n降至2或更低)。由于n = 2 k ,这意味着k = log 2 n,因此采用的平方根数为O(log k)= O(log log n) 。因此,如果存在通过将问题重复地减少到原始问题大小的平方根大小的子问题而工作的算法,则该算法将在O(log log n)步骤之后终止。

一个真实的例子是van Emde Boas tree(vEB-tree)数据结构。 vEB树是用于存储0 ... N-1范围内的整数的专用数据结构。它的工作原理如下:树的根节点中有√N指针,分割范围0 ... N - 1到√N桶,每个桶包含大约√N整数的范围。然后将这些铲斗内部细分为√(√N)铲斗,每个铲斗大致保持√(√N)个元素。要遍历树,从根开始,确定您属于哪个存储桶,然后以相应的子树递归继续。由于vEB树的结构方式,您可以在O(1)时间内确定要下降到哪个子树,因此在O(log log N)步后,您将到达树的底部。因此,vEB树中的查找仅花费时间O(log log N)。

另一个例子是Hopcroft-Fortune closest pair of points algorithm。该算法试图找到2D点集合中的两个最接近的点。它的工作原理是创建一个桶网格并将这些点分配到这些桶中。如果在算法中的任何点处发现其中具有多于√N个点的桶,则该算法递归地处理该桶。因此递归的最大深度为O(log log n),并且使用递归树的分析可以显示树中的每个层都执行O(n)工作。因此,算法的总运行时间为O(n log log n)。

O(log n)小输入算法

还有一些其他算法通过在大小为O(log n)的对象上使用二进制搜索等算法来实现O(log log n)运行时。例如,x-fast trie数据结构对高度为O(log U)的树的层执行二进制搜索,因此其某些操作的运行时为O(log log U)。相关的y-fast trie通过维护每个O(log U)节点的平衡BST来获得它的一些O(日志日志U)运行时,允许在那些树中搜索在时间O(日志日志U)中运行。 tango tree和相关multisplay tree数据结构在其分析中最终得到O(log log n)项,因为它们维护的树每个都包含O(log n)项。

其他例子

其他算法以其他方式实现运行时O(log log n)。 Interpolation search期望运行时O(log log n)在排序数组中找到一个数字,但分析相当复杂。最后,分析的工作原理是迭代次数等于数k,使得n 2 -k ≤2,其中log log n是正确的解决方案。一些算法,如Cheriton-Tarjan MST algorithm,通过解决复杂的约束优化问题,到达涉及O(log log n)的运行时。

希望这有帮助!

答案 1 :(得分:3)

在时间复杂度中看待O因子(log log n)的一种方法是通过除法在另一个答案中解释的东西,但是当我们想要在时间和时间之间进行交易时,还有另一种方法可以看到这个因素。算法的空间/时间和近似/时间和硬度/ ...我们对算法进行了一些人工迭代。

例如SSSP(单源最短路径)在平面图上有一个O(n)算法,但在那个复杂的算法之前,有一个更简单的算法(但仍然相当困难),运行时间为O(n log log n ),算法的基础如下(只是非常粗略的描述,我提议跳过理解这部分并阅读答案的其他部分):

  1. 将图形划分为大小为O的部分(log n /(log log n)),但有一些限制。
  2. 假设每个提到的部分是新图G'中的节点,然后在时间O中计算G'的SSSP(| G'| * log | G'|)==>因为| G'| = O(| G | * log log n / log n)我们可以看到(log log n)因子。
  3. 为每个部分计算SSSP:再次因为我们有O(| G'|)部分,我们可以及时计算所有部分的SSSP | n / logn | * | log n / log logn * log(logn / log log n)。
  4. 更新权重,这部分可以在O(n)中完成。 有关详细信息this lecture notes很好。
  5. 但我的观点是,在这里我们选择大小为O的分区(log n /(log log n))。如果我们选择其他分区,如O(log n /(log log n)^ 2),它可能运行得更快并带来另一个结果。我的意思是,在许多情况下(如近似算法或随机算法,或像上面的SSSP这样的算法),当我们迭代某些东西(子问题,可能的解决方案......)时,我们选择对应于该交易的迭代次数我们有(算法的时间/空间/复杂性/算法的常数因子,......)。因此,在实际工作算法中,我们可能会看到比“log log n”更复杂的东西。