Find number of unique routes to specific node using Depth First Search

泪湿孤枕 提交于 2019-12-09 00:46:59

问题


I have a directed graph with vertexes 123456. Using a depth first search, if I wanted to be able to find the number of unique routes from 1-4 (for example), how would I go about doing it? Here is my current DFS.

private final Map<Character, Node> mNodes;
private final List<Edge> mEdges;
private List<Node> mVisited = new ArrayList<>();
int weight;
int numberOfPaths;

public DepthFirstSearch(Graph graph){
    mNodes = graph.getNodes();
    mEdges = new ArrayList<>(graph.getEdges());
    for(Node node : mNodes.values()){
        node.setVisited(false);
    }
}

public void depthFirstSearch(Node source){

    source.setVisited(true);
    List<Edge> neighbours = source.getNeighbouringEdges(mEdges);
    for(Edge edge : neighbours){
        System.out.println(edge.getFrom().getName()+"-"+edge.getTo().getName());
        if(!edge.getTo().isVisited()){

            mVisited.add(edge.getTo());
            weight += edge.getWeight();
            depthFirstSearch(edge.getTo());

        }
    }

}

回答1:


Since cycles aren't allowed, you have actually a DAG (directed acyclid graph).

These are some questions related to this topic:

  • Number of paths between two nodes in a DAG
  • Topological sort to find the number of paths to t

Basically, the idea is get a topological sort of the DAG, then iterate the nodes starting from the destination node backwards to the source node, calculating how many paths are from that node.

Since the array haven't cycles and the nodes are topological sorted, when you visit a node, all nodes that can be reached from that node appears latter in the sort and are already visited. So, the count of paths from an node to the target is the sum of the counts of the nodes that are adjacent to it.

Models:

class Graph
{
    private List<Node> nodes;
    private List<Edge> edges;

    public Graph() {
        nodes = new ArrayList<>();
        edges = new ArrayList<>();
    }

    public List<Node> getNodes() { return nodes; }    
    public List<Edge> getEdges() { return edges; }

    public void addNode(Node node) { nodes.add(node); }    
    public void addEdge(Edge edge) {
        edges.add(edge);        
        edge.getFrom().addEdge(edge);
        edge.getTo().addEdge(edge);
    }
}

class Node
{
    private List<Edge> outgoings, incomings;

    public Node() {
        outgoings = new ArrayList<>();
        incomings = new ArrayList<>();
    }

    public List<Edge> getOutgoings() { return outgoings; }    
    public List<Edge> getIncomings() { return incomings; }

    public void addEdge(Edge edge) {
        if (edge.getFrom() == this) outgoings.add(edge);
        if (edge.getTo() == this) incomings.add(edge);
    }
}

class Edge
{
    private Node from, to;

    public Edge(Node from, Node to) {
        this.from = from;
        this.to = to;
    }

    public Node getFrom() { return from; }    
    public Node getTo() { return to; }
}

Algorithm:

static int countPaths(Graph graph, Node source, Node target)
{
    List<Node> nodes = topologicalSort(graph);
    int[] counts = new int[nodes.size()];

    int srcIndex = nodes.indexOf(source);
    int tgtIndex = nodes.indexOf(target);

    counts[tgtIndex] = 1;

    for (int i = tgtIndex; i >= srcIndex; i--) {
        for (Edge edge : nodes.get(i).getOutgoings())
            counts[i] += counts[nodes.indexOf(edge.getTo())];
    }

    return counts[srcIndex];
}

static List<Node> topologicalSort(Graph g)
{
    List<Node> result = new ArrayList<>();
    Set<Node> visited = new HashSet<>();
    Set<Node> expanded = new HashSet<>();

    for (Node node: g.getNodes())
        explore(node, g, result, visited, expanded);

    return result;
}

static void explore(Node node, Graph g, List<Node> ordering, Set<Node> visited, Set<Node> expanded) {
    if (visited.contains(node)) {            
        if (expanded.contains(node)) return;
        throw new IllegalArgumentException("Graph contains a cycle.");
    }

    visited.add(node);

    for (Edge edge: node.getIncomings())
        explore(edge.getFrom(), g, ordering, visited, expanded);

    ordering.add(node);
    expanded.add(node);
}

Usage:

Graph g = new Graph();

Node a = new Node();
Node b = new Node();
Node c = new Node();
Node d = new Node();
Node e = new Node();

Edge ab = new Edge(a, b);
Edge bc = new Edge(b, c);
Edge cd = new Edge(c, d);
Edge ad = new Edge(a, d);
Edge ae = new Edge(a, e);
Edge ed = new Edge(e, d);
Edge be = new Edge(b, e);
Edge ec = new Edge(e, c);

g.addNode(a);
g.addNode(b);
g.addNode(c);
g.addNode(d);
g.addNode(e);

g.addEdge(ab);
g.addEdge(bc);
g.addEdge(cd);
g.addEdge(ad);
g.addEdge(ae);
g.addEdge(ed);
g.addEdge(be);
g.addEdge(ec);

int count = countPaths(g, a, d); 

//count: 6

// Paths:
//1. A->B->C->D
//2. A->D
//3. A->E->D
//4. A->B->E->C->D
//5. A->B->E->D
//6. A->E->C->D

But, if you want to do it using a BFS, you can try doing a backtrack resetting the visited nodes:

static int countPaths(Graph graph, Node source, Node target)
{
    Set<Node> visiteds = new HashSet<>();
    return BFS(source, target, visiteds);
}

static int BFS(Node current, Node target, Set<Node> visiteds) {
    if (current == target) return 1;
    else
    {
        int count = 0;
        visiteds.add(current);

        for (Edge edge : current.getOutgoings())
            if (!visiteds.contains(edge.getTo()))
                count += BFS(edge.getTo(), target, visiteds);

        visiteds.remove(current);
        return count;
    }
}    

However, to increase the performance, you can implement some kind of memoization:

static int countPaths(Graph graph, Node source, Node target)
{
    Set<Node> visiteds = new HashSet<>();
    Map<Node, Integer> memoize = new HashMap<>();

    for (Node node : graph.getNodes())
        memoize.put(node, -1);

    memoize.put(target, 1);

    return BFS(source, visiteds, memoize);
}

static int BFS(Node current, Set<Node> visiteds, Map<Node, Integer> memoize) {
    if (memoize.get(current) != -1) return memoize.get(current);
    else
    {
        int count = 0;
        visiteds.add(current);

        for (Edge edge : current.getOutgoings())
            if (!visiteds.contains(edge.getTo()))
                count += BFS(edge.getTo(), visiteds, memoize);

        visiteds.remove(current);
        memoize.put(current, count);
        return count;
    }
}  



回答2:


(Assuming directed acyclic graph)

All you need to do is to run your DFS and count every discovered route which leads to the destination node.

The mistake in your code is that your setVisited() and isVisited() state is global. This means that whenever you encounter node C you mark it as visited and it stays marked so for the whole DFS run -- even for paths where it actually has not been visited yet.

An example DFS run (given simple graph A -> B, B -> C, A -> C):

  • (Step 1 -- path A) Visit A, mark A visited

  • (Step 1.1 -- path A -> B) Visit B, mark B visited

  • (Step 1.1.1 -- path A -> B -> C) Visit C, mark C visited

  • (Step 1.2 -- path A -> C) Here you skip this route as C is marked as visited from step 1.1.1 (which is wrong)

You need to trace visited nodes correctly by e.g.:

  • trust the caller that the input graph really is acyclic and do not trace visited nodes at all (as there is no way to visit a single node twice in an acyclic graph). You risk that your program enter infinite recursion for wrong input

  • use simpler/cheaper cycle detection. You can trace the depth of recursion and when it gets deeper than the total number of nodes in the graph raise an exception

  • reset the node visited state right after the visit (this is what @Arturo Menchaca suggests in his great answer)

  • keep a separate visited state for each route being processed

Example java code which traces visited nodes in a list (this way you can print the discovered routes):

package test.java.so;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class So37503760 {

    public static class Graph {

        private final Map<Character, Node> nodes = new TreeMap<Character, Node>();

        public Node addNode(char c) {
            Node existingNode = nodes.get(c);
            if(existingNode!=null) {
                return existingNode;
            }
            Node newNode = new Node(c);
            nodes.put(c, newNode);
            return newNode;
        }

        public void addEdge(char f, char t) {
            addNode(f).addChild(addNode(t));
        }

        public Node getNode(char name) {
            Node ret = nodes.get(name);
            if(ret==null) {
                throw new RuntimeException("No such node " + name);
            }
            return ret;
        }

    }

    public static class Node {

        private final char name;
        private final ArrayList<Node> children = new ArrayList<Node>();

        public Node(char c) {
            this.name=c;
        }

        public void addChild(Node childNode) {
            children.add(childNode);
        }

        public ArrayList<Node> getChildren() {
            return children;
        }

        public char getName() {
            return name;
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.addEdge('A', 'B');
        graph.addEdge('A', 'C');
        graph.addEdge('B', 'C');
        graph.addEdge('C', 'D');
        graph.addEdge('C', 'E');
        graph.addEdge('D', 'E');
        int numberOfPaths = depthFirstSearch(graph, 'A', 'E');
        System.out.println("Found " + numberOfPaths + " paths");
    }

    public static int depthFirstSearch(Graph graph, char startNode, char destinationNode){
        return depthFirstSearch(graph, graph.getNode(startNode), graph.getNode(destinationNode), new ArrayList<Node>());
    }

    public static int depthFirstSearch(Graph graph, Node startNode, Node destinationNode, List<Node> currentPath){
        if(currentPath.contains(startNode)) {
            currentPath.add(startNode);
            throw new RuntimeException("Cycle detected: " + pathToString(currentPath));
        }
        currentPath.add(startNode);
        try {
//          System.out.println("Progress: " + pathToString(currentPath));
            if(startNode==destinationNode) {
                System.out.println("Found path: " + pathToString(currentPath));
                return 1;
            }
            int ret=0;
            for(Node nextNode : startNode.getChildren()){
                ret += depthFirstSearch(graph, nextNode, destinationNode, currentPath);
            }
            return ret;
        } finally {
            currentPath.remove(currentPath.size()-1);
        }
    }

    private static String pathToString(List<Node> path) {
        StringBuilder b = new StringBuilder();
        boolean printArrow=false;
        for(Node n : path) {
            if(printArrow) {
                b.append(" -> ");
            }
            b.append(n.getName());
            printArrow=true;
        }
        return b.toString();
    }
}



回答3:


It really depends on the edges of your graph. If all of your vertices are connected to one another it would be very simple because you'd get the same number of paths every time (1->4, 1->2->4, 1->3->4, 1->5->4,...,1->6->5->3->2->4 [going with the paths from 1 to 4 example). It would look very similar given any n->m path (given the fact that you want no cycles), and there would be the same number of paths each time. However, if there are any vertices that aren't connected to other vertices, that's where it would get interesting. You'd probably want to use a modified Djikstra's algorithm that would then give you the different answers (I'm kind of guessing you're looking for) for number of unique paths.



来源:https://stackoverflow.com/questions/37503760/find-number-of-unique-routes-to-specific-node-using-depth-first-search

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!