依赖性算法 - 找到要安装的最小软件包集

时间:2015-05-25 00:30:04

标签: java python algorithm mathematical-optimization minimization

我正在研究一种算法,其目标是找到一组最小的软件包来安装软件包“X”。

我会用一个例子来解释:

X depends on A and (E or C)
A depends on E and (H or Y)
E depends on B and (Z or Y)
C depends on (A or K)
H depends on nothing
Y depends on nothing
Z depends on nothing
K depends on nothing

解决方案是安装:A E B Y。

这是描述示例的图像:

是否有一种算法可以在不使用蛮力方法的情况下解决问题?

我已经阅读了很多关于DFS,BFS,Dijkstra等算法的内容...... 问题是这些算法无法处理“OR”条件。

更新

我不想使用外部库。

该算法不必处理循环依赖。

更新

一种可能的解决方案是计算每个顶点的所有可能路径,并且对于可能路径中的每个顶点,执行相同的操作。 因此,X的可能路径是(A E),(A C)。现在,对于这两个可能路径中的每个元素,我们可以做同样的事情:A =(E H),(E Y)/ E =(B Z),(B Y),依此类推...... 最后,我们可以组合SET中每个顶点的可能路径,并选择长度最短的路径。

您怎么看?

10 个答案:

答案 0 :(得分:8)

不幸的是,考虑到问题实际上是NP-hard(但不是NP-complete),找到一个比蛮力更好的算法是没有希望的。

这个问题的NP-硬度的证明是最小vertex cover问题(众所周知的是NP-hard而不是NP-complete)很容易被简化为:

给出一个图表。让我们为图的每个顶点 v 创建包 P v 。还要创建包 X 什么“和”-requires( P u P v )对于图的每个边(u,v)。找到要安装的最小软件包集,以满足 X 。然后 v 位于图iff的最小顶点覆盖中,相应的包 P v 位于安装集中。

答案 1 :(得分:2)

“我用”或“来解决问题(图片没有为我加载)。 这是我的推理。假设我们采用标准的最短路线算法,如Dijkstras,然后使用等同的权重来找到最佳路径。 举个例子 从以下2个选项中选择最佳Xr

Xr= X+Ar+Er
Xr= X+Ar+Cr

其中Ar =是树A = H(及后续孩子)或A = Y(及后续孩子)的最佳选择

这个想法是首先为每个或选项分配标准重量(因为选项不是问题)。 然后,对于每个或选项,我们使用其子节点重复该过程,直到我们不再接受或选择。

但是,我们需要首先定义,最佳选择意味着什么,假设最少数量的依赖关系,即最短路径是标准。 通过上面的逻辑我们为X指定权重1。从那时起

X=1
X=A and E or C hence X=A1+E1 and X=A1+C1
A= H or Y, assuming H and Y are  leaf node hence A get final weight as 1
hence , X=1+E1 and X=1+C1

Now for E and C
E1=B1+Z1 and B1+Y1 . C1=A1 and C=K1.
Assuming B1,Z1,Y1,A1and K1 are leaf node 

E1=1+1 and 1+1 . C1=1 and C1=1
ie E=2 and C=1

Hence
X=1+2 and X=1+1 hence please choose X=>C as the best route

希望这清除它。 此外,我们需要处理周期性依赖关系X => Y => Z => X,这里我们可以指定这些节点在父节点或叶节点级别为零,并照顾依赖性。“

答案 2 :(得分:2)

我实际上认为图表是这个问题的合适结构。注意 A和(E或C)< ==> (A和E)或(A和C)。因此,我们可以用以下一组有向边来表示X = A和(E或C):

A <- K1
E <- K1
A <- K2
C <- K2
K1 <- X
K2 <- X

基本上,我们只是分解语句的逻辑并使用&#34; dummy&#34;表示AND的节点。

假设我们以这种方式分解所有逻辑语句(对于ANDS和有向边的虚拟Ki节点,否则)。然后,我们可以将输入表示为DAG并递归遍历DAG。我认为以下递归算法可以解决问题:

说明:
节点u - 当前节点。
S - 访问的节点集。
children(x) - 返回x的out邻居。

算法:

shortestPath u S = 
if (u has no children) {
    add u to S
    return 1
} else if (u is a dummy node) {
  (a,b) = children(u)
  if (a and b are in S) {
    return 0
  } else if (b is in S) { 
    x = shortestPath a S
    add a to S
    return x
  } else if (a in S) {
    y = shortestPath b S
    add b to S
    return y
  } else {
    x = shortestPath a S
    add a to S
    if (b in S) return x
    else {
        y = shortestPath b S
        add b to S
        return x + y
    }
  }
} else {
  min = Int.Max
  min_node = m
  for (x in children(u)){
    if (x is not in S) {
      S_1 = S
      k = shortestPath x S_1
      if (k < min) min = k, min_node = x
    } else {
      min = 1
      min_node = x
    }
  }
  return 1 + min
}

分析: 这是一个完全顺序的算法(我认为)最多遍历每个边缘一次。

答案 3 :(得分:2)

这里的许多答案都集中在这是一个理论上难以解决的问题,因为它具有NP难的状态。虽然这意味着您将体验到渐进性能很差的解决问题(根据当前的解决方案技术),您仍然可以快速(足够)解决您的特定问题数据。例如,尽管问题在理论上具有挑战性,但我们能够准确地解决庞大的旅行商问题实例。

在您的情况下,解决问题的方法是将其表示为混合整数线性程序,其中每个包x_i都有一个二进制变量i。您可以将需求A requires (B or C or D) and (E or F) and (G)转换为x_A <= x_B + x_C + x_D ; x_A <= x_E + x_F ; x_A <= x_G形式的约束,并且您可以要求将P包括在x_P = 1的最终解决方案中。准确地解决这样的模型是相对简单的;例如,您可以在python中使用纸浆包:

import pulp

deps = {"X": [("A"), ("E", "C")],
        "A": [("E"), ("H", "Y")],
        "E": [("B"), ("Z", "Y")],
        "C": [("A", "K")],
        "H": [],
        "B": [],
        "Y": [],
        "Z": [],
        "K": []}
required = ["X"]

# Variables
x = pulp.LpVariable.dicts("x", deps.keys(), lowBound=0, upBound=1, cat=pulp.LpInteger)

mod = pulp.LpProblem("Package Optimization", pulp.LpMinimize)

# Objective
mod += sum([x[k] for k in deps])

# Dependencies
for k in deps:
    for dep in deps[k]:
        mod += x[k] <= sum([x[d] for d in dep])

# Include required variables
for r in required:
    mod += x[r] == 1

# Solve
mod.solve()
for k in deps:
    print "Package", k, "used:", x[k].value()

这将输出最小的包集:

Package A used: 1.0
Package C used: 0.0
Package B used: 1.0
Package E used: 1.0
Package H used: 0.0
Package Y used: 1.0
Package X used: 1.0
Package K used: 0.0
Package Z used: 0.0

对于非常大的问题实例,这可能需要很长时间才能解决。您可以使用超时接受潜在的次优解决方案(请参阅here),或者您可以从默认的开源解算器转移到商业求解器,如gurobi或cplex,这可能会更快。

答案 4 :(得分:1)

添加到Misandrist的答案:你的问题是 NP-complete NP-hard(见dened的回答)。

编辑:这是将Set Cover实例(U,S)直接缩减为“包问题”实例:将地面集U的每个点z设为一个AND要求对于X.使用S中的每个集合覆盖点z和z的OR要求。然后包装问题的解决方案给出了最小的设置覆盖率。

等效地,您可以询问单调布尔电路的哪个令人满意的分配具有最少的真实变量,请参阅这些lecture notes

答案 5 :(得分:1)

由于图形由两种不同类型的边(AND和OR关系)组成,我们可以将算法分成两部分:搜索所有节点所需的后继节点并搜索我们必须从中选择的所有节点一个节点(OR)。

节点包含一个包,一个必须是该节点后继节点的节点列表(AND),一个可以作为该节点后继节点的节点列表(OR)以及一个标记算法中哪一步的标志该节点已被访问。

define node: package p , list required , listlist optional , 
             int visited[default=MAX_VALUE]

主程序将输入转换为图形并在起始节点处开始遍历。

define searchMinimumP:
    input: package start , string[] constraints
    output: list

    //generate a graph from the given constraint
    //and save the node holding start as starting point
    node r = getNode(generateGraph(constraints) , start)

    //list all required nodes
    return requiredNodes(r , 0)

requiredNodes搜索节点所需的所有节点(通过1个或多个边缘的AND-relation连接到n)。

define requiredNodes:
    input: node n , int step
    output: list

    //generate a list of all nodes that MUST be part of the solution
    list rNodes
    list todo

    add(todo , n)

    while NOT isEmpty(todo)
        node next = remove(0 , todo)
        if NOT contains(rNodes , next) AND next.visited > step
            add(rNodes , next)
            next.visited = step

    addAll(rNodes , optionalMin(rNodes , step + 1))

    for node r in rNodes
        r.visited = step

    return rNodes

optimalMin在可选邻居(OR)的所有可能解决方案中搜索最短的解决方案。该算法是强力的(将检查邻居的所有可能选择。

define optionalMin:
    input: list nodes , int step
    output: list

    //find all possible combinations for selectable packages
    listlist optSeq
    for node n in nodes
        if NOT n.visited < step
            for list opt in n.optional
                add(optSeq , opt)

    //iterate over all possible combinations of selectable packages
    //for the given list of nodes and find the shortest solution
    list shortest
    int curLen = MAX_VALUE

    //search through all possible solutions (combinations of nodes)
    for list seq in sequences(optSeq)
        list subseq

        for node n in distinct(seq)
            addAll(subseq , requiredNodes(n , step + 1))

        if length(subseq) < curLen
            //mark all nodes of the old solution as unvisited
            for node n in shortest
                n.visited = MAX_VALUE

            curLen = length(subseq)
            shortest = subseq
        else
            //mark all nodes in this possible solution as unvisited
            //since they aren't used in the final solution (not at this place)
            for node n in subseq
                n.visited = MAX_VALUE

     for node n in shorest
         n.visited = step

     return shortest

基本思想如下:从起始节点开始,搜索必须属于解决方案的所有节点(只能通过遍历AND关系从起始节点到达的节点)。现在,对于所有这些节点,算法会搜索可选节点(OR)与所需节点最少的组合。

注意:到目前为止,这种算法并不比蛮力好得多。我会在找到更好的方法后立即更新。

答案 6 :(得分:1)

我的代码是here

<强>情境:

代表约束。

X : A&(E|C)
A : E&(Y|N)
E : B&(Z|Y)
C : A|K

准备两个变量目标和结果。 将节点X添加到目标。

target = X, result=[]

将单个节点X添加到结果中。 将节点X替换为其在目标中的依赖。

target = A&(E|C), result=[X]

将单个节点A添加到结果中。 将节点A替换为其在目标中的依赖。

target = E&(Y|N)&(E|C), result=[X, A]

单个节点E必须为true。 所以(E | C)总是如此。 将其从目标中移除。

target = E&(Y|N), result=[X, A]

将单个节点E添加到结果中。 将节点E替换为其在目标中的依赖。

target = B&(Z|Y)&(Y|N), result=[X, A, E]

将单个节点B添加到结果中。 将节点B替换为其在目标中的依赖。

target = (Z|Y)&(Y|N), result=[X, A, E, B]

不再有单个节点。 然后展开目标表达式。

target = Z&Y|Z&N|Y&Y|Y&N, result=[X, A, E, B]

将Y&amp; Y替换为Y。

target = Z&Y|Z&N|Y|Y&N, result=[X, A, E, B]

选择节点数最少的术语。 将术语中的所有节点添加到目标。

target = , result=[X, A, E, B, Y]

答案 7 :(得分:1)

我建议您先在AND-OR Tree中转换图表。完成后,您可以在树中执行最佳搜索(您可以选择“最佳”的含义:节点中包的最短,最低内存占用等)路径。

我要做的一个建议,就是安装X的条件类似install(X) = install(A) and (install(E) or install(C)),就是将OR节点(在本例中为:E和C)分组到一个节点,比如EC,并转换install(X) = install(A) and install(EC)中的条件。

另外,根据AND-OR树的想法,您可以使用分组思想创建自定义AND-OR图。通过这种方式,您可以使用graph traversal算法的自适应,这在某些情况下可能更有用。

另一种解决方案可能是使用Forward Chaining。您必须按照以下步骤操作:

  1. 转换(只需在此处重写条件):

    A和(E或C)=&gt; X

    E和(H或Y)=&gt;甲

    B和(Z或Y)=&gt; ë

  2. (A and E) or (A and C) => X
    (E and H) or (E and Y) => A
    (B and Z) or (B and Y) => E
    
    1. 将X设为目标。
    2. 插入B,H,K,Y,Z作为事实。
    3. 向前运行链接并在第一次出现X(目标)时停止。这应该是在这种情况下实现目标的最短路径(只记得跟踪已经使用过的事实)。
    4. 如果有什么不清楚,请告诉我。

答案 8 :(得分:0)

这是Constraint Satisfaction Problem的示例。有许多语言的Constraint Solvers,甚至可以在通用3SAT引擎上运行的一些语言,因此可以在GPGPU上运行。

答案 9 :(得分:0)

解决此问题的另一种(有趣的)方法是使用遗传算法。

遗传算法功能强大,但您必须使用大量参数并找到更好的参数。

遗传步骤如下:

a。 创作:一些随机个体,第一代 (例如:100)

变异:变异为低百分比(例如:0.5%)

℃。 评分:对所有个人进行评分(也称为健身)。

d。 复制:选择(使用费率)一对并创建子项(例如:2个孩子)

选择:选择父级和子级以创建新一代(例如:按代保留100个人)

F。 循环:返回步骤“a”并重复所有过程多次(例如:400代)

克。 选择:选择具有最高费率的上一代个人。 个人将成为您的解决方案。

以下是您必须决定的事项:

  1. 为您的个人找到遗传密码
  2. 您必须将问题的可能解决方案(称为个人)表示为遗传密码。

    在您的情况下,它可以是一组代表节点的字母,它遵守约束OR和NOT。

    例如:

      

    [A E B Y],[A C K H],[A E Z B Y] ......

    1. 找一种评估个人费用的方法
    2. 要知道某个人是否是一个好的解决方案,您必须对其进行评分,以便将其与其他人进行比较。

      在您的情况下,它可能非常简单:单个速率=节点数 - 单个节点的数量

      例如:

        

      [A E B Y] = 8 - 4 = 4

           

      [A E Z B Y] = 8 - 5 = 3

           

      [A E B Y]的效率优于[A E Z B Y]

      1. 选择
      2. 由于个人的费率,我们可以选择一对来进行复制。

        例如,使用Genetic Algorithm roulette wheel selection

        1. 复制
        2. 从他们那里拿一对个人创造一些(例如2个)孩子(其他个人)。

          例如:

          从第一个节点获取节点并将其与第二个节点交换。

          进行一些调整以适应“或”和“约束”。

            

          [A E B Y],[A C K H] =&gt; [A C E H B Y],[A E C K B Y]

          注意:这不是重现它的好方法,因为孩子比父母更有价值。也许我们可以交换一系列节点。

          1. 突变
          2. 您只需更改选择个体的遗传密码即可。

            例如:

            • 删除节点

            • 进行一些调整以适应“或”和“约束”。

            正如您所看到的,实施并不难,但必须做出很多选择来设计具体问题并控制不同的参数(突变百分比,比率系统,繁殖系统,个体数量,代数,...)