为DAG构建所有路径算法

时间:2013-02-14 23:59:29

标签: java algorithm directed-acyclic-graphs

尝试构建一种方法,通过DAG获取所有可想到的唯一路径。接受递归,因为它似乎最容易理解。结束了这个:

public class Brutus {
    //the previous nodes visited
    public ArrayList<Node> resultHistory = new ArrayList<Node>();
    //Directed Graph class, contains a HashMap [adjacencies]
    // that has keys for all nodes that contains all edges
    public AdjacencyList paths;
    //A list of all the pathways between nodes represented as a list of nodes
    public ArrayList<ArrayList<Node>> allPaths = new ArrayList<ArrayList<Node>>();

public Brutus(AdjacencyList paths) {
    this.paths = paths;
}

public ArrayList<ArrayList<Node>> findAll() {
    int counter = 1;
    for (Node key : paths.adjacencies.keySet()) {
        System.out.println("[" + counter + "]: " + key.toString());
        StringTokenizer st = new StringTokenizer(
            paths.getAdjacentString(key), ",");
        while (st.hasMoreTokens()) {
            String child = st.nextToken();
            if (paths.getNodeFromGraph(child) != null) {
                resultHistory = new ArrayList<Node>();
                resultHistory.add(key);
                findPath(child, resultHistory);
            }
        }
        counter++;
    }
    return allPaths;
}

public void findPath(String child, ArrayList<Node> resultHistory) {
    if (resultHistory.contains(paths.getNodeFromGraph(child))) {
        return;
    }
    resultHistory.add(paths.getNodeFromGraph(child));
    if(!(inList(resultHistory, allPaths))) {
        allPaths.add(resultHistory);
    }
    StringTokenizer st = new StringTokenizer(
        paths.getAdjacentString(paths.getNodeFromGraph(child)), ",");
    while (st.hasMoreTokens()) {
        child = st.nextToken();
        if (paths.getNodeFromGraph(child) != null) {
            findPath(child, resultHistory);
        }

    }
}

public boolean inList(ArrayList<Node> resultHistory,
    ArrayList<ArrayList<Node>> allPaths) {
    for (int i = 0; i < allPaths.size();i++) {
        if (allPaths.get(i).equals(resultHistory)) {
            return true;
        }
    }
    return false;
}

问题是,我不认为它适用于所有路径,因为我无法在其中找到某些路径。虽然数据集是900个节点,但我无法找到模式!关于Stack的其他问题似乎更专业,因此我尝试构建自己的算法!

任何人都可以建议一种更好的方式来执行此操作,或者告诉我我做错了什么? 如果算法正确,那么撤销两个节点之间所有路径的最佳方法是什么?

编辑:我现在意识到新路径不是从原始的子节点创建的,我怎么能这样做呢?

3 个答案:

答案 0 :(得分:3)

这是一个简单的递归算法,用伪代码表示,以避免大量Java列表操作的问题:

AllPaths(currentNode):
  result = EmptyList()
  foreach child in children(node):
    subpaths = AllPaths(child)
    foreach subpath in subpaths:
      Append(result, currentNode + subpath)
  return result

在根节点上调用AllPaths将为您提供所需的内容,并且您可以通过在每个节点上缓存AllPaths的结果来改善非平凡DAG的运行时间,因此您只需要计算每一条包含它的不同路径,而不是一次。

答案 1 :(得分:3)

这里有一个基于BFS算法的实现。 我将路径表示为顶点序列l = (v, v', v'', ...),我将对其执行以下两个操作:

  • extend(l, v):将顶点v放在列表l的末尾;
  • v = back(l):获取列表l中的最后一个顶点。

FindPaths(G, v) {
  // The first path is, simply, the starting node.
  // It should be the first vertex in topological order.
  pending_paths = {(v)};
  while (pending_paths is not empty) {
    l = pending_paths.remove_first(); // pop the first pending path
    output(l); // output it (or save it in a list to be returned, if you prefer)
    v = back(l); // Get the last vertex of the path
    foreach(edge (v, v') in G) { // For each edge outgoing from v'...
      // extend l with v' and put into the list of paths to be examined.
      pending_paths.push_back(extend(l, v'));
    } 
  }
}

答案 2 :(得分:2)

所以虽然@ akappa的Pseudo是一个好的开始,但我花了一些时间来理解如何让它工作,如果有人在这里发现这个帖子我就是这样做的:

public ArrayList<ArrayList<Node>> searchAll() {
    try {
        BufferedWriter out = new BufferedWriter(new FileWriter("output.txt"));
        //Gets Nodes from Hashmap and puts them into Queue
        for (Node key : paths.adjacencies.keySet()) {
            queue.addToQueue(new QueueItem(key.chemName, new ArrayList<Node>()));
        }
        while (queue.getSize() > 0) {
            QueueItem queueItem = queue.getFromQueue();
            Node node = paths.getNodeFromGraph(queueItem.getNodeId());
            if (node != null) {
                findRelationAll(node, queueItem, out);
            }
        }
        System.out.println("Cycle complete: Number of Edges: [" + resultHistoryAll.size() + "]");
        out.close();
    } catch (IOException e) {
    }
    return resultHistoryAll;

}

public void findRelationAll(Node node, QueueItem queueItem, BufferedWriter out) {
    if (!foundRelation) {
        StringTokenizer st = new StringTokenizer(paths.getAdjacentString(node), ",");
        while (st.hasMoreTokens()) {
            String child = st.nextToken();
            ArrayList<Node> history = new ArrayList<Node>();
            //Gets previous Nodes
            history.addAll(queueItem.getHistoryPath());
            //Makes sure path is unique
            if (history.contains(node)) {
                System.out.println("[" + counter2 + "]: Cyclic");
                counter2++;
                continue;
            }
            history.add(node);
            resultHistory = history;
            queue.addToQueue(new QueueItem(child, history));
            if (!(inList(resultHistory, resultHistoryAll))) {
                resultHistoryAll.add(resultHistory);
                try {
                    out.write("[" + counter + "]: " + resultHistory.toString());
                    out.newLine();
                    out.newLine();
                } catch (IOException e) {
                }
                System.out.println("[" + counter + "]: " + resultHistory.toString());
                counter++;
            } else {
                System.out.println("[" + counter3 + "]: InList");
                counter3++;
            }

        }
    }
}
//This checks path isn't in List already
public boolean inList(ArrayList<Node> resultHistory, ArrayList<ArrayList<Node>> allPaths) {
    for (int i = 0; i < allPaths.size(); i++) {
        if (allPaths.get(i).equals(resultHistory)) {
            return true;
        }
    }
    return false;
}

}

上面的代码会做一些你可能不想要的事情:

  • 它将文本路径的路径写为节点列表+它的计数器值。
  • 确保路径不会跨越同一节点两次
  • 确保最终列表中没有两条路径相同(在正常情况下这是不必要的工作)

QueueItem对象只是存储以前访问过的节点的一种方法。它是nemanja's代码的一部分,这是我的代码所基于的。

向他提示,akappa(最有效的答案)和jacobm(用于找到像我原始代码的解决方案,并解释它的局限性)。

任何人真正对这项工作感兴趣;我目前正处理超过500万个途径,其中60,000个是900种化学品之间的独特途径。这只是1,2,3或4种化学途径......生物学很复杂。

编辑和警告:如果有人正在处理像我这样的大量数据,Windows 7 - 或者至少是我的机器 - 抛出一个合适的东西并在ArrayList&gt;之后关闭程序。 63,000个对象,无论你如何安排指针。我开始使用的解决方案是在60,000个对象之后,重新启动列表,同时将所有内容添加到CSV。这导致了列表迭代之间的一些重复,并且最终应该被我明天转移到linux上超越!