递归深度展平的时间复杂度

时间:2018-10-11 05:14:52

标签: javascript recursion big-o

此递归展平函数的运行时是什么?我的猜测是线性的。有人可以解释为什么吗?

Timer

1 个答案:

答案 0 :(得分:2)

正如评论中指出的那样,由于每个元素确实仅被触摸过一次,因此时间复杂度直观地 O(N)

  

但是,由于每次对flatten的递归调用都会创建一个新的中间数组,因此运行时在很大程度上取决于输入数组的结构。


这种情况的一个不平凡的 1 例子是当数组的组织类似于完整的二叉树时:

[[[a, b], [c, d]], [[e, f], [g, h]]], [[[i, j], [k, l]], [[m, n], [o, p]]]

               |
        ______ + ______
       |               |
    __ + __         __ + __
   |       |       |       |
 _ + _   _ + _   _ + _   _ + _
| | | | | | | | | | | | | | | | 
a b c d e f g h i j k l m n o p

时间复杂度递归关系为:

T(n) = 2 * T(n / 2) + O(n)

其中2 * T(n / 2)来自对flatten子树的递归调用,而O(n)来自push 2 结果,它们是长度为n / 2的两个数组。

  

Master theorem声明在这种情况下为 T(N) = O(N log N) ,而不是预期的O(N)

1)非平凡表示没有元素被不必要地包裹,例如[[[a]]]

2)这隐式地假设k推送操作是O(k)摊销的,这不是标准所保证的,但对于大多数实现而言仍然是正确的。


“ true” O(N)解决方案将直接附加到 final 输出数组,而不是创建中间数组:

function flatten_linear(items) {
  const flat = [];

  // do not call the whole function recursively
  // ... that's this mule function's job
  function inner(input) {
     if (Array.isArray(input))
        input.forEach(inner);
     else
        flat.push(input);
  }

  // call on the "root" array
  inner(input);  

  return flat;
}

在前面的示例中,递归变为T(n) = 2 * T(n / 2) + O(1),这是线性的。

再次假设1)和2)。