以下编码可用于任何具有N
节点和M
边的图。它使用(N+1)*M
变量和2*M*M
3-SAT 子句。这个ipython 笔记本通过比较 SAT 求解器结果(存在循环时为 UNSAT,否则为 SAT)与直接循环查找算法的结果来演示编码。
免责声明:此编码是我对该问题的临时解决方案。我很确定它是正确的,但我不知道它在性能方面与其他编码相比如何解决这个问题。由于我的解决方案适用于任何图形,因此可以预期存在更好的解决方案,该解决方案使用 OP 感兴趣的图形类的某些属性。
变量:
每条边都有一个变量。如果设置了相应的变量,则边缘是“活动的”或“使用的”。在我的参考实现中,边缘有索引0..(M-1)
,而这个变量有索引1..M
:
def edge_state_var(edge_idx):
assert 0 <= edge_idx < M
return 1 + edge_idx
然后我M
为每条边设置一个位宽的状态变量,或者总共有一个N*M
状态位(节点和位也使用从零开始的索引):
def node_state_var(node_idx, bit_idx):
assert 0 <= node_idx < N
assert 0 <= bit_idx < M
return 1 + M + node_idx*M + bit_idx
条款:
当一条边处于活动状态时,它将连接的两个节点的状态变量链接在一起。与该节点具有相同索引的状态位必须在两侧不同,并且其他状态位必须等于其在另一个节点上的对应伙伴。在python代码中:
# which edge connects which nodes
connectivity = [
( 0, 1), # edge E0
( 1, 2), # edge E1
( 2, 3), # edge E2
( 0, 4), # edge E3
...
]
cnf = list()
for i in range(M):
eb = edge_state_var(i)
p, q = connectivity[i]
for k in range(M):
pb = node_state_var(p, k)
qb = node_state_var(q, k)
if k == i:
# eb -> (pb != qb)
cnf.append([-eb, -pb, -qb])
cnf.append([-eb, +pb, +qb])
else:
# eb -> (pb == qb)
cnf.append([-eb, -pb, +qb])
cnf.append([-eb, +pb, -qb])
所以基本上每条边都试图将它所在的图分割成位于边一侧的一半,并将与该边相对应的所有状态位设置为 1 和位于另一侧的一半,并且将对应于边的状态位设置为 0。对于循环中的所有节点都可以从循环中每个边的两侧到达的循环来说,这是不可能的。