递归幂集函数的时间复杂度
Posted
技术标签:
【中文标题】递归幂集函数的时间复杂度【英文标题】:Time Complexity of recursive Power Set function 【发布时间】:2022-01-19 11:13:15 【问题描述】:我在简化用于查找给定输入集的幂集的递归算法的时间复杂度方面遇到了麻烦。到目前为止,我也不完全确定我所得到的是否正确。
在此链接的页面底部有描述:http://www.ecst.csuchico.edu/~akeuneke/foo/csci356/notes/ch1/solutions/recursionSol.html
通过考虑函数为任意选择的大小为 4 的输入集采取的每个步骤,然后将其转换为大小为 n 的输入集,我得出的结果是,在 Big-O 表示法方面的时间复杂度算法是:2nnn
这是正确的吗?是否有一种特定的方法可以找到递归函数的时间复杂度?
【问题讨论】:
找到递归函数时间复杂度的标准方法是用数学方法将其运行时间表示为 recurrence relation 并找到您定义的递归关系的封闭形式解决方案,或者上限。但有时您仍然可以更一般地对算法进行推理 【参考方案1】:运行时间实际上是 O(n*2n)。简单的解释是,这是一种渐近最优算法,因为它所做的总工作主要是通过创建直接在算法的最终输出中具有特征的子集来支配,生成的输出的总长度为 O(n*2n)。我们还可以分析伪代码的注释实现(在 javascript 中),以更严格地显示这种复杂性:
function powerSet(S)
if (S.length == 0) return [[]] // O(1)
let e = S.pop() // O(1)
let pSetWithoutE = powerSet(S); // T(n-1)
let pSet = pSetWithoutE // O(1)
pSet.push(...pSetWithoutE.map(set => set.concat(e))) // O(2*|T(n-1)| + ||T(n-1)||)
return pSet; // O(1)
// print example:
console.log('');
for (let subset of powerSet([1,2,3])) console.log(`\t`, subset.join(', '), ``);
console.log('')
其中T(n-1)
表示递归调用 n-1 个元素的运行时间,|T(n-1)|
表示递归调用返回的幂集中子集的数量,||T(n-1)||
表示总数量递归调用返回的所有子集的元素。
在这些术语中表示的具有复杂性的行对应于伪代码步骤2.
的第二个要点:返回没有元素e
的幂集的联合,以及与每个子集s
联合的相同幂集e
:
(1) U ((2) = s in (1) U e)
这个联合是通过 push 和 concat 操作来实现的。 push
在|T(n-1)|
时间将(1)
与(2)
合并,因为|T(n-1)|
新子集被合并到幂集中。 concat
操作的映射负责通过在|T(n-1)| + ||T(n-1)||
时间将e
附加到pSetWithoutE
的每个元素来生成(2)
。这第二种复杂性对应于|T(n-1)|
子集pSetWithoutE
(根据定义)中存在||T(n-1)||
元素,并且每个子集的大小都增加1。
然后我们可以将输入大小n
的运行时间表示为:
T(n) = T(n-1) + 2|T(n-1)| + ||T(n-1)|| + 1; T(0) = 1
可以通过归纳证明:
|T(n)| = 2n
||T(n)|| = n2n-1
产生:
T(n) = T(n-1) + 2*2<sup>n-1</sup> + (n-1)2<sup>n-2</sup> + 1; T(0) = 1
当你解析地解决这个递归关系时,你会得到:
T(n) = n + 2<sup>n</sup> + n/2*2<sup>n</sup> = O(n2<sup>n</sup>)
这与最佳幂集生成算法的预期复杂度相匹配。递推关系的解也可以直观理解:
n 次迭代中的每一次 O(1) 都在生成幂集的新子集之外工作,因此最终表达式中的 n
项。
就生成幂集的每个子集所做的工作而言,每个子集在通过 concat 生成后被推送一次。推送了 2n 个子集,产生了 2n 项。每个子集的平均长度为 n/2,组合长度为 n/2*2n,对应于所有 concat 操作的复杂性。因此,总时间为 n + 2n + n/2*2n。
【讨论】:
以上是关于递归幂集函数的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章