解决依赖关系和冲突的图形(可能包含周期)

时间:2013-01-28 03:15:45

标签: graph dependencies conflict boolean-logic boolean-operations

给出一个图表,其中每个节点可能包含与其他节点的任意依赖关系或冲突,导致任何可能的安排,包括循环和矛盾的引用。

我正在尝试计算一个最大限度的稳定结果列表,其中包含能够遵守所有约束的节点列表。我只需找到一种可能的解决方案,如果有的话。

在下面的例子中,“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,它在非平凡的图形上非常失败。任何见解,阅读材料的指针或算法的名称都将非常感激。

2 个答案:

答案 0 :(得分:2)

我认为

  • “A取决于B”表示 A表示B
  • “B与A冲突”表示 B表示不是A

现在,你有 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函数并替换dependsOnconflictsWith函数来匹配:

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。

请注意,此处显示的实现是粗略的,当然可以针对速度进行优化。