一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情。
与子集问题不同的另一种经典问题是求排列。与纯粹的子集问题和最初的笛卡尔积问题不一样,对于排列问题而言,实际上要做的事是在一个不断减少的子集中选取当前位置的元素。
如果我们从m叉树的角度看待这个问题,那么我们可能需要引入一个已访问的标记。这样的话和从dfs的角度是殊途同归的(也就是认为所有的点都是互相连接的,通过标记并取消标记得到不同的生成树)。
但是我们可以有另一种思路,具体来说,对于每个位置,尝试把还没有选取的元素换到当前位置,这是每次选择还没选过的值放到当前位置。此外,在选择下一个值之前,我们要记得撤销本次交换。否则会出现重复的结果。
总而言之,最终我们的代码如下:
def permutation(n: int):
res = []
X = [i for i in range(1, n + 1)]
def perm(d: int):
if d == n:
res.append(X.copy())
else:
for i in range(d, n):
X[d], X[i] = X[i], X[d]
perm(d + 1)
X[d], X[i] = X[i], X[d]
perm(0)
return res
这里有一个添加 X.copy() 原因在于这个值是引用传递,如果是计算一个确定的无重复集合的排列,那么可以由生成的结果保存相关值。
类似地,这类回溯也是有剪枝的。
我们从经典的旅行商问题开始。
由于旅行商问题实际上是枚举一个环,这意味着从任意的顶点出发都会得到等价的结果。因此不妨假设从首个位置开始,也就是不再枚举第一位的值。 而后考虑剪枝的操作,当我们选了下一个到达的城市,如果这一步会导致开销比当前的最优花费还要大,那么就没必要继续搜索了。
因此具体到代码的实现像下面这样:
def tspsolve(g):
n = len(g)
x, bx = [i for i in range(n)], []
bc = float('inf')
def tsp(d: int, c):
nonlocal bc, bx
if d >= n and c + g[x[n - 1]][0] < bc:
bc, bx = c + g[x[n - 1]][0], x.copy()
elif d < n:
for i in range(d, n):
x[d], x[i] = x[i], x[d]
if c + g[x[d - 1]][x[d]] < bc:
tsp(d + 1, c + g[x[d - 1]][x[d]])
x[d], x[i] = x[i], x[d]
tsp(1, 0)
return bx, bc
注意bx也是使用了copy(),此外由于是环形的路径,最终还要再计算一次结果。