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

Posted

技术标签:

【中文标题】递归深度展平的时间复杂度【英文标题】: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;

【问题讨论】:

我也认为它在元素数量上是线性的,因为最终你需要触摸每个元素一次,因为它被移动到一个单级数组。 ***.com/a/30048623/2630817可以看看吗? @TimBiegeleisen 尽管O(N) 很直观,但它取决于数组的递归结构,因为正在为每个嵌套数组创建中间缓冲区。 【参考方案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 是可能的最小分支因子);算法本身不对输入的结构做任何假设(除了循环引用等愚蠢的边缘情况)

以上是关于递归深度展平的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

递归展平列表[重复]

递归的空间复杂度

关于递归程序的时间复杂度

如何使用 Hive/Pig/MapReduce 展平递归层次结构

Leetcode二叉树的最小深度

使用 Databricks 和 ADF 展平复杂的 json