递归深度展平的时间复杂度
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)
来自push
ing2 结果,它们是两个长度为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 是可能的最小分支因子);算法本身不对输入的结构做任何假设(除了循环引用等愚蠢的边缘情况)以上是关于递归深度展平的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章