置换的阶乘时间复杂度
Posted
技术标签:
【中文标题】置换的阶乘时间复杂度【英文标题】:Factorial Time Complexity for Permutations 【发布时间】:2022-01-22 16:42:40 【问题描述】:我只想检查以下代码是否具有阶乘时间复杂度。 IE。 O(n!) 如果 n 是 my_str
中的字符数。据我了解,它有,但我可能错过了一些东西。
def perms(a_str):
stack = list(a_str)
results = [stack.pop()]
while stack:
current = stack.pop()
new_results = []
for partial in results:
for i in range(len(partial) + 1):
new_results.append(partial[:i] + current + partial[i:])
results = new_results
return results
my_str = "ABCDEFGHIJ"
print(perms(my_str))
【问题讨论】:
输出的大小为 O(n! * n),所以没有比这更快的算法了。否则,您认为您的算法可能比这慢吗? @kaya3 有趣的事实:itertools.permutations
可能是 O(n!) 如果我们允许它重用其结果元组。 Benchmark 和 code。不过,不确定更新算法的复杂性。
@kaya3 假设它的成本与从一个排列到下一个排列的变化数量成正比,它does seem 每个排列的恒定成本(每个排列有 2.63 个变化)。
@inordirection 是的,我读过关于单交换置换器的文章,但我对 itertools 的实现特别好奇。是的,完全使用排列当然会贡献另一个因素 n。关于复制先前的结果元组:这就是我的第一条评论。我认为不是。如果它检测到它具有对元组的唯一引用,则将其更改到位。元组仅从 Python 代码的角度是不可变的。
@inordirection 是的,或者如果您不使用所有排列,例如,如果您随机采样所有排列的 1/n(并完全查看每个采样的排列)。循环在 Python 中没有自己的作用域。如果你不删除p
,那么它在下一次迭代和整个循环之后仍然存在。
【参考方案1】:
复杂度实际上是 O((n+1)!),虽然与 O(n!) 相当,但复杂度明显比它大。
将算法放入适合其运行时分析的术语中,它迭代地生成输入字符串的每个后缀的所有排列,直到 while 循环的最后一次迭代完成,它将构建整个输入字符串的排列。
在循环之前,您生成最后一个长度为 1 的后缀的所有排列(仅包含最后一个字符的所有 1!= 1 个排列的列表)。为简单起见,将此视为循环的第一次迭代
然后,在循环的k-th
迭代期间,您可以有效地使用后缀a_str[n-k+1:]
的所有先前排列来构建递增后缀a_str[n-k:]
的排列,方法是将索引n-k
处的字符放入您已经建立的每个部分排列的所有可能位置。每次迭代完成的总工作量与在该迭代期间生成的所有新部分排列字符串的总长度成正比,即每个部分排列的长度,k
,乘以部分排列的数量,k!
:@ 987654331@.
考虑到k
的范围可以从1
(在生成最后一个字符的初始单个排列时)到n
(在最后一次迭代期间负责生成最终出现在输出),在整个算法过程中完成的总工作可以通过简单的和给出:
当您求解这个总和时,表示在算法过程中构建的所有部分排列的总长度,您会得到:
置换生成算法的最佳运行时间为O(n*n!)
,因为这是您需要生成的输出数组的总长度。但是,O((n+1)!) = O(n*n!)
因为:
O((n+1)!) = O((n+1)n!) = O(n*n! + n!) = O(n*n!)
这意味着上述算法仍然是渐近最优的,即使它在构建部分排列方面确实做了一些不必要的工作,这些部分排列本身并不直接反映在最终输出中(例如,基于交换元素而不是基于交换元素的排列生成算法迭代构建部分排列可能会稍微快一些)。
你可以用这个工具版本的算法和一些测试用例来检查我的数学:
def perms(a_str):
total_cost = 0 # total cost of creating all partial permutation strings
stack = list(a_str)
results = [stack.pop()]
total_cost += len(results[0]) # increment total cost
while stack:
current = stack.pop()
new_results = []
for partial in results:
for i in range(len(partial) + 1):
next = partial[:i] + current + partial[i:]
total_cost += len(next) # increment total cost
new_results.append(next)
results = new_results
return results, total_cost
from math import factorial
def test(string):
n = len(string)
print(f'perms(string):')
ps, total_cost = perms(string)
# print(ps)
print("optimal cost:", n * factorial(n))
print("total cost:", total_cost)
print("expected cost (sum):", sum([k * factorial(k) for k in range(1, n+1)]))
print("expected cost (closed form):", factorial(n + 1) - 1)
print()
tests = [''.join([chr(i) for i in range(ord('A'), ord('A')+j)]) for j in range(1, ord('I') - ord('A'))]
for t in tests: test(t)
【讨论】:
以上是关于置换的阶乘时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章