通过递归排列数组,何时进行第二个元素交换?
Permutation of an array by recursion, when does the second element swap take place?
这个排列程序中的第二个元素交换是如何工作的?
第二次交换发生在程序的哪一步?在第一次递归之后还是在所有递归完成之后?
def permutations(A: List[int]) -> List[List[int]]:
def directed_permutations(i):
if i == len(A) - 1:
result.append(A.copy())
return
# Try every possibility for A[i]
for j in range(i, len(A)):
A[i], A[j] = A[j], A[i]
# Generate all permutations for A[i + 1:]
directed_permutations(i + 1)
# Second element swap, which I do not understand
A[i], A[j] = A[j], A[i]
result: List[List[int]] = []
directed_permutations(0)
return result
当我离开第二个交换并尝试 A = [2,3,5,7] 我得到 4!元素,但有重复:
[[2, 3, 5, 7], [2, 3, 7, 5], [2, 7, 3, 5], [2, 7, 5, 3], [2, 3 , 5, 7], [2, 3, 7, 5], [3, 2, 7, 5], [3, 2, 5, 7], [3, 5, 2, 7], [3, 5 , 7, 2], [3, 2, 7, 5], [3, 2, 5, 7], [5, 2, 3, 7], [5, 2, 7, 3], [5, 7 , 2, 3], [5, 7, 3, 2], [5, 2, 3, 7], [5, 2, 7, 3], [3, 2, 7, 5], [3, 2 , 5, 7], [3, 5, 2, 7], [3, 5, 7, 2], [3, 2, 7, 5], [3, 2, 5, 7]]
通过第二次交换,我得到了正确的 4!元素:
[[2, 3, 5, 7], [2, 3, 7, 5], [2, 5, 3, 7], [2, 5, 7, 3], [2, 7 , 5, 3], [2, 7, 3, 5], [3, 2, 5, 7], [3, 2, 7, 5], [3, 5, 2, 7], [3, 5 , 7, 2], [3, 7, 5, 2], [3, 7, 2, 5], [5, 3, 2, 7], [5, 3, 7, 2], [5, 2 , 3, 7], [5, 2, 7, 3], [5, 7, 2, 3], [5, 7, 3, 2], [7, 3, 5, 2], [7, 3 , 2, 5], [7, 5, 3, 2], [7, 5, 2, 3], [7, 2, 5, 3], [7, 2, 3, 5]]
递归的规则是(几乎总是)你不要让子问题中的状态改变改变父状态。每个调用框架不应该知道或关心(可能遥远的)子调用中发生的事情,从而保留递归自相似性。参数数据结构应该是有效的不可变的。
post-递归调用交换通过执行调用前交换的撤消来实现不变性,return将列表恢复到循环迭代之前的状态。这种状态恢复对于正确性至关重要,因为没有它,循环的下一次迭代(以及之后的每一次迭代)将有一个列表,该列表已被先前迭代中的任意嵌套子调用修改,从而破坏了每个循环的有条不紊的枚举方法递归调用框架交换 i
处的元素与每个元素 j
,然后递归从 i + 1
开始的列表的其余部分,并为剩余的子调用固定 0..i
.
为了表明不可变性是关键因素,而撤消交换只是实现这一点的一种手段,您可以编写算法,在将参数列表作为参数传递时复制参数列表:
from typing import List
def permutations(A: List[int]) -> List[List[int]]:
result = []
def directed_permutations(A, i=0):
if i == len(A) - 1:
result.append(A)
else:
for j in range(i, len(A)):
B = A[:]
B[i], B[j] = B[j], B[i]
directed_permutations(B, i + 1)
directed_permutations(A)
return result
if __name__ == "__main__":
print(permutations([1, 2, 3]))
请注意,我们不再需要在附加到结果时进行复制,因为现在每个帧都有一个唯一的列表可以进行操作,而无需担心混叠。不过,与原始方法相比,这种方法会产生更多的复制开销。
这不是重点,但我会 return 像这样的算法的生成器,就像 itertools
那样。
这个排列程序中的第二个元素交换是如何工作的? 第二次交换发生在程序的哪一步?在第一次递归之后还是在所有递归完成之后?
def permutations(A: List[int]) -> List[List[int]]:
def directed_permutations(i):
if i == len(A) - 1:
result.append(A.copy())
return
# Try every possibility for A[i]
for j in range(i, len(A)):
A[i], A[j] = A[j], A[i]
# Generate all permutations for A[i + 1:]
directed_permutations(i + 1)
# Second element swap, which I do not understand
A[i], A[j] = A[j], A[i]
result: List[List[int]] = []
directed_permutations(0)
return result
当我离开第二个交换并尝试 A = [2,3,5,7] 我得到 4!元素,但有重复:
[[2, 3, 5, 7], [2, 3, 7, 5], [2, 7, 3, 5], [2, 7, 5, 3], [2, 3 , 5, 7], [2, 3, 7, 5], [3, 2, 7, 5], [3, 2, 5, 7], [3, 5, 2, 7], [3, 5 , 7, 2], [3, 2, 7, 5], [3, 2, 5, 7], [5, 2, 3, 7], [5, 2, 7, 3], [5, 7 , 2, 3], [5, 7, 3, 2], [5, 2, 3, 7], [5, 2, 7, 3], [3, 2, 7, 5], [3, 2 , 5, 7], [3, 5, 2, 7], [3, 5, 7, 2], [3, 2, 7, 5], [3, 2, 5, 7]]
通过第二次交换,我得到了正确的 4!元素:
[[2, 3, 5, 7], [2, 3, 7, 5], [2, 5, 3, 7], [2, 5, 7, 3], [2, 7 , 5, 3], [2, 7, 3, 5], [3, 2, 5, 7], [3, 2, 7, 5], [3, 5, 2, 7], [3, 5 , 7, 2], [3, 7, 5, 2], [3, 7, 2, 5], [5, 3, 2, 7], [5, 3, 7, 2], [5, 2 , 3, 7], [5, 2, 7, 3], [5, 7, 2, 3], [5, 7, 3, 2], [7, 3, 5, 2], [7, 3 , 2, 5], [7, 5, 3, 2], [7, 5, 2, 3], [7, 2, 5, 3], [7, 2, 3, 5]]
递归的规则是(几乎总是)你不要让子问题中的状态改变改变父状态。每个调用框架不应该知道或关心(可能遥远的)子调用中发生的事情,从而保留递归自相似性。参数数据结构应该是有效的不可变的。
post-递归调用交换通过执行调用前交换的撤消来实现不变性,return将列表恢复到循环迭代之前的状态。这种状态恢复对于正确性至关重要,因为没有它,循环的下一次迭代(以及之后的每一次迭代)将有一个列表,该列表已被先前迭代中的任意嵌套子调用修改,从而破坏了每个循环的有条不紊的枚举方法递归调用框架交换 i
处的元素与每个元素 j
,然后递归从 i + 1
开始的列表的其余部分,并为剩余的子调用固定 0..i
.
为了表明不可变性是关键因素,而撤消交换只是实现这一点的一种手段,您可以编写算法,在将参数列表作为参数传递时复制参数列表:
from typing import List
def permutations(A: List[int]) -> List[List[int]]:
result = []
def directed_permutations(A, i=0):
if i == len(A) - 1:
result.append(A)
else:
for j in range(i, len(A)):
B = A[:]
B[i], B[j] = B[j], B[i]
directed_permutations(B, i + 1)
directed_permutations(A)
return result
if __name__ == "__main__":
print(permutations([1, 2, 3]))
请注意,我们不再需要在附加到结果时进行复制,因为现在每个帧都有一个唯一的列表可以进行操作,而无需担心混叠。不过,与原始方法相比,这种方法会产生更多的复制开销。
这不是重点,但我会 return 像这样的算法的生成器,就像 itertools
那样。