【问题标题】:Resolving graphs (possibly containing cycles) of dependancies and conflicts解决依赖关系和冲突的图(可能包含循环)
【发布时间】:2013-01-11 08:58:34
【问题描述】:

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

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

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

【问题讨论】:

  • 为了澄清预期的输出,您能否 1) 指出运算符的优先级(我认为您假设优先级为 and > or)?以及 2)解释为什么 A没有出现在第三个示例的答案中?目前,我不确定为什么需要“依赖”行,除了显示在评估冲突时应该考虑哪些节点。如果没有“取决于”行,问题似乎可以解决为“如果 A 则不是 B / 如果 B 则不是 A”等形式的一组布尔语句。感谢您的澄清。
  • 嗨西蒙,我更新了上面的问题来尝试回答你的问题。
  • 有人可以验证一下吗:“A 依赖于 B”=A 暗示 B。“B 与 A 冲突”=B 暗示不是 A。如果是这种情况,那么有一个简单的多项式时间算法。
  • @Dan:在测试我的原始答案时,我发现您的解释对于给出 OP 提供的示例输出是必要的,所以我认为他/她(和我,因为我有一个可能的未来项目可能需要这样的东西)会发现指向多项式时间算法的指针很有用。谢谢。
  • @Simon:我添加了这个问题的答案。

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


【解决方案1】:

我认为

  • “A 依赖于 B”表示 A 隐含 B
  • “B 与 A 冲突”表示 B 暗示不是 A

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

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

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

【讨论】:

  • 嗨,丹,非常感谢。维基百科的图表甚至看起来怪异地像我一直在绘制的图表,试图弄清楚这一点。谢谢。
  • 不客气。我很高兴我的 CS 学习最终得到回报;)
【解决方案2】:

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

"A 依赖于 B" => "(a and b) or (not 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 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。

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

【讨论】:

  • @user1887190:很高兴为您提供帮助。我注意到您已经接受然后不接受这个答案。如果我可以进一步提供帮助,请告诉我。
  • 指数时间要求使得该方案无法使用。我希望有某种我不知道的图行走算法可以用来解决这类问题。我能找到的一切只有在没有循环的情况下才有效。据我所知,这是唯一的解决方案,在这种情况下,我非常乐意接受您的回答。
  • @user1887190:是的,指数时间要求对于非常大的案例来说是个问题。我会考虑替代方案。例如,可以通过首先考虑“冲突”标准来划分问题。同时,您是否知道问题在您的应用程序中可能会有多大?谢谢。
  • @user1887190:我想到有一些方法可以避免测试我们已经知道不包含潜在解决方案的案例子集,所以我编辑了我的答案以结合一种方法来做到这一点。
  • @Dan:好主意。 Stack Overflow 对 satsolvers here 有很好的问题和答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-16
  • 1970-01-01
  • 2014-09-06
  • 1970-01-01
  • 2023-02-02
  • 2018-10-13
  • 1970-01-01
相关资源
最近更新 更多