给出一个图表,其中每个节点可能包含与其他节点的任意依赖关系或冲突,导致任何可能的安排,包括循环和矛盾的引用。
我正在尝试计算一个最大限度的稳定结果列表,其中包含能够遵守所有约束的节点列表。我只需找到一种可能的解决方案,如果有的话。
在下面的例子中,“A取决于B”表示A为真B必须为真。 “B与A冲突”意味着B为真A不得为真。没有优先级依赖和冲突具有相同的权重并同时应用。
第三个例子'A'没有出现,因为它依赖于D与B冲突。因为A也需要B .. A不能作为D的冲突而存在,A的依赖性禁止它。
A depends on B
B conflicts with A
= B
A depends on B
B depends on A
= A and B
A depends on B
A depends on D
B depends on C
D conflicts with B
D conflicts with C
= B and C or D
A conflicts with B
B conflicts with C
C conflicts with D
D conflicts with A
= A and C or B and D
我一直在尝试提出一种有效的算法,但到目前为止,我的努力类似于启发式的gobblygook,它在非平凡的图形上非常失败。任何见解,阅读材料的指针或算法的名称都将非常感激。
答案 0 :(得分:2)
我认为
现在,你有 A暗示B =不是A或B 而 B暗示不是A =不是B或不是A 。这意味着问题归结为找到一个析取连接的解决方案(也称为子句),其中每个子句有两个参数(A,不是A,B或不是B)。
这个问题被称为2可满足性。您可以在网络中找到多项式时间算法,例如,从http://en.wikipedia.org/wiki/2-satisfiability开始。
据我了解现代SAT求解器的分辨率,不需要编写自己的算法。 SAT求解器应该能够在多项式时间内自动解决此类实例。
答案 1 :(得分:1)
将问题中使用的语言翻译成布尔表达式,我们有:
“A取决于B”=> “(a和b)或(不是a)”
和
“B与A冲突”=> “(b而非a)或(而非b)”
因此,编写此代码(在Python 3中)的一种简单方法是生成笛卡尔积(以提供所有可能的替代),然后仅选择满足约束的情况。为简单起见,我使用索引而不是字母作为输入,因此y[0]
等同于A
等。但我已将输出转换为字母。
对于n个节点,此方法将生成并测试所有2 ^ n个可能的情况。请参阅下文,了解更复杂但可能更有效的方法。
import itertools
bbb = (True,False)
def resolve(n,test):
return [x for x in itertools.product(bbb,repeat=n) if test(x)]
def dependsOn(a,b):
return (a and b) or (not a)
def conflictsWith(b,a):
return (b and not a) or (not b)
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
def pr(d):
for dd in d:
s = [letters[i] for i in range(len(dd)) if dd[i] ]
if (len(s) > 0):
print(s,end = " ")
print()
pr(list(resolve(2,lambda y:
dependsOn(y[0],y[1]) and
conflictsWith(y[1],y[0])
)))
pr(list(resolve(2,lambda y:
dependsOn(y[0],y[1]) and
dependsOn(y[1],y[0])
)))
pr(list(resolve(4,lambda y:
dependsOn(y[0],y[1]) and
dependsOn(y[0],y[3]) and
dependsOn(y[1],y[2]) and
conflictsWith(y[3],y[1]) and
conflictsWith(y[3],y[2])
)))
pr(list(resolve(4,lambda y:
conflictsWith(y[0],y[1]) and
conflictsWith(y[1],y[2]) and
conflictsWith(y[2],y[3]) and
conflictsWith(y[3],y[0])
)))
这给出了结果:
['B']
['A', 'B']
['B', 'C'] ['C'] ['D']
['A', 'C'] ['A'] ['B', 'D'] ['B'] ['C'] ['D']
...对于四个测试用例。
有关详情,请查看Wikipedia entry on truth tables。
(编辑)
对于许多节点和许多约束的问题,一种更有效的方法是逐步构建节点列表,然后只有在部分列表符合约束的情况下才从每个部分列表继续构建,至少在它具有的范围内到目前为止已经填充。我们可以通过使用以下版本替换上面的resolve
函数并替换dependsOn
和conflictsWith
函数来匹配:
import queue
# The following constraint functions return True if the constraint is met
# or if one or more of the elements relating to the constraint is None
def dependsOn(a,b):
if (a != None) and (b != None):
return (a and b) or (not a)
else:
return True
def conflictsWith(b,a):
if (a != None) and (b != None):
return (b and not a) or (not b)
else:
return True
def resolve(n,test):
result = []
testCount = 0
# initialise the list with all None
lst = [None] * n
q = queue.Queue()
q.put(lst)
while not q.empty():
lst = list(q.get())
testCount += 1
if test(lst):
# the list complies with the constraints, at least
# as far as it is populated
if lst[-1] != None:
# we have a fully-populated list
result.append(lst)
else:
i = lst.index(None)
lst[i] = True
q.put(list(lst))
lst[i] = False
q.put(list(lst))
print ("testCount = %d" % testCount)
return result
这给出了四个测试用例的相同结果。但是,对于第三和第四个测试用例,testCount
的值分别为21和23。这超过了笛卡尔积解决方案所需的测试总数(n = 4时为16)但是,对于有更多节点和更多约束的情况,这种方法避免了测试不能的节点子集可能包含一个解决方案,因此很容易比笛卡尔产品解决方案需要更少的测试。当然,在极少或没有约束的最坏情况下,这种方法可能需要最多2 ^(n + 1)-1次测试。事实上,这确实发生在前两个测试用例中,testCount
使用此算法为7。
请注意,此处显示的实现是粗略的,当然可以针对速度进行优化。