2

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

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

在下面的示例中,“A 依赖于 B”意味着 A 为真 B 必须为真。“B 与 A 冲突”意味着 B 为真 A 一定不为真。依赖和冲突都没有优先级,具有相同的权重并同时应用。

在第三个示例中,“A”没有出现,因为它依赖于与 B 冲突的 D。因为 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,它在非平凡的图表上失败了。任何见解,阅读材料的指针或算法名称将不胜感激。

4

2 回答 2

2

我假设

  • “A 依赖于 B”意味着A 隐含 B并且
  • “B 与 A 冲突”意味着B 不暗示 A

现在,您有A 意味着 B = not A or BB 意味着 not A = not B or not A。这意味着问题归结为找到析取合取(也称为子句)的解决方案,其中每个子句都有两个参数(A,不是 A,B 或不是 B)。

这个问题被称为 2-satisfiability。您可以在网络中找到多项式时间算法,例如,从http://en.wikipedia.org/wiki/2-satisfiability开始。

据我了解现代 SAT 求解器的分辨率,没有必要编写自己的算法。SAT 求解器应该能够在多项式时间内自动求解此类实例。

于 2013-02-01T07:58:45.943 回答
1

将问题中使用的语言翻译成布尔表达式,我们有:

“A 取决于 B”=>“(a 和 b)或(非 a)”

"B 与 A 冲突" => "(b and not a) or (not 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 条目

(编辑)

对于具有许多节点和许多约束的问题,一种更有效的方法是逐步构建节点列表,然后仅在该部分列表符合约束的情况下从每个部分列表继续构建,至少在它已被填充的范围内远的。我们可以通过将resolve上面的函数替换为以下版本并替换dependsOnandconflictsWith函数来匹配:

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。

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

于 2013-01-28T21:58:43.373 回答