【问题标题】:Time complexity for recrusive deep flatten递归深度展平的时间复杂度
【发布时间】:2019-03-16 02:23:24
【问题描述】:

这个递归展平函数的运行时间是多少?我的猜测是它是线性的。谁能解释一下为什么?

const arr = [
  [14, [45, 60], 6, [47, [1, 2, [14, [45, 60], 6, [47, [1, 2]], 9]]], 9],
];

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

  items.forEach(item => {
    if (Array.isArray(item)) {
      flat.push(...flatten(item));
    } else {
      flat.push(item);
    }
  });

  return flat;
}

【问题讨论】:

  • 我也认为它在元素数量上是线性的,因为最终你需要触摸每个元素一次,因为它被移动到一个单级数组。
  • @TimBiegeleisen 尽管O(N) 很直观,但它取决于数组的递归结构,因为正在为每个嵌套数组创建中间缓冲区。

标签: javascript recursion big-o


【解决方案1】:

正如 cmets 中所指出的,由于每个元素确实只被触摸一次,因此时间复杂度直观地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) 来自pushing2 结果,它们是两个长度为n / 2 的数组。

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

1) non-trivial 表示没有元素被不必要地包装,例如[[[a]]].

2) 这隐含地假设k 推送操作是O(k) 摊销的,标准不保证这一点,但对于大多数实现来说仍然如此。


“真”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(items);  

  return flat;
}

对于前面的示例,重复变为T(n) = 2 * T(n / 2) + O(1),这是线性的。

再次假设 1) 和 2)。

【讨论】:

  • 你为什么选择 T(n/b) 作为 T(n/2)?
  • @Undefined 时间复杂度计算特定于上面的示例树状数组结构(当然 2 是可能的最小分支因子);算法本身不对输入的结构做任何假设(除了循环引用等愚蠢的边缘情况)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-16
  • 2020-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-23
  • 2011-02-12
相关资源
最近更新 更多