0

I am currently doing an assignment where we need to find a path from a node on an undirected graph, the start node, to the destination node without changing the type of edge (each edge is labelled with a letter) more than a specified amount of times. (E.g. If only one change is allowed, then going from an edge labelled 'a' to an edge labelled 'b' will use up that one change. But going from 'a' to 'a' will not use up any changes). We can only use Depth First Search (DFS) for traversing the tree. The graph is in a rectangular/square shaped grid format, so each node can have a minimum of 1 or 2 edges (these nodes are either just connected to one node or at the corner of the graph and connected to two nodes) and a maximum of 4 edges.

My algorithm uses DFS to traverse through the graph and finds every solution/path possible from start to end, then finds the number of changes each solution/path would require and will then return the solution that requires the minimum amount of changes if that number of changes is smaller or equal to the number of allowed changes. This does work most of the time, however, when the graph size is 18 nodes across x 18 nodes down, and above, it takes too long to come up with an answer or just plain crashes.

So I am just wondering if there is a much more efficient way of doing this? Is there any way to alter my code to make it more efficient?

//The solver method that returns the solution
public Iterator<GraphNode> solve() throws GraphException {
    GraphNode startNode = graph.getNode(startLoc); //Creates the starting node.
    GraphNode endNode = graph.getNode(endLoc); //Creates the ending node.

    pathDepthFirstSearch(graph, startNode, endNode);

    int smallest = findSmallestChangeSolution(listOfSolutions);
    if(smallest == -1) {
        return null;
    }

    return listOfSolutions.get(smallest).iterator();
}

//DFS traversal and add the nodes along a path to an ArrayList.
private void pathDepthFirstSearch(Graph graph, GraphNode u, GraphNode v) throws GraphException {
    listOfNodes.add(u);
    u.setMark(true);
    if(u.getName() == v.getName()) {
        addSolutionToList(new ArrayList<GraphNode>(listOfNodes));
    } else {
        for (Iterator<GraphEdge> iter = graph.incidentEdges(u); iter.hasNext();) {
            GraphEdge nextEdge = iter.next();
            GraphNode secondEndPoint = nextEdge.secondEndpoint();
            if(secondEndPoint.getMark() == false) {
                pathDepthFirstSearch(graph,secondEndPoint, v);
            }
        }
    }
    listOfNodes.remove(u);
    u.setMark(false);
}

//Adds the each solution to an ArrayList
private void addSolutionToList(ArrayList<GraphNode> list) {
    ArrayList<GraphNode> tempList = new ArrayList<GraphNode>();
    for (int i = 0; i < list.size(); i++) {
        tempList.add(list.get(i));
    }
    listOfSolutions.add(tempList);
}

//Finds the solution with the smallest number of changes and returns the
//index of the solution list with that number of changes.
private int findSmallestChangeSolution(ArrayList<ArrayList<GraphNode>> list) throws GraphException {
    int changes = 0;
    int[] changesForEachSolution = new int[list.size()];
    for (int i = 0; i < list.size(); i++) {
        for(int j = 0; j < list.get(i).size() - 2; j++) {
            if(graph.getEdge(list.get(i).get(j), list.get(i).get(j+1)).getLabel() != graph.getEdge(list.get(i).get(j+1), list.get(i).get(j+2)).getLabel()) {
                changes++; //Increments the number of changes by 1 if the two consecutive edges have different labels.
            }
        }
        changesForEachSolution[i] = changes;
        changes = 0; //Resets the number of changes to 0 for the next solution.
    }

    //Finds the position of the solution with the smallest number of changes.
    int smallest = changesForEachSolution[0];
    int indexOfSmallest = 0;
    for(int i = 0; i < changesForEachSolution.length; i++) {
        if(changesForEachSolution[i] < smallest) {
            smallest = changesForEachSolution[i];
            indexOfSmallest = i;
        }
    }

    //If the smallest number of changes is larger than the allowed number of changes, no solution exists, so return -1.
    if(smallest > kNumOfChanges) {
        return -1;
    }
    //Otherwise, the index of the solution is returned.
    return indexOfSmallest;
}

I have also tried altering the code a bit so that the recursive calls in the DFS method stops if a valid solution is found, but it didn't seem to make a difference with larger graphs (anything larger than 18 x 18).

Pengibaby
  • 373
  • 2
  • 11

1 Answers1

1

Here are two possibilities to speed up your solution:

  1. Pruning. You do not need to proceed with the search if you know that your path so far has already exceeded the allowed budget of label switches. That is, you can have variables changesSoFar and lastEdgeLabel passed down to your pathDepthFirstSearch function. Increase changesSoFar every time you proceed with an edge whose label differs from lastEdgeLabel and exit the function if changesSoFar exceeds the maximum allowed number of switches.

    You may prune the search further if you keep track of the current best known path and leave the function whenever listOfNodes.size() >= bestPathLengthSoFar. This will not be needed, however, if you rely on

  2. Iterative deepening. DFS is, in general, not the right method for finding shortest paths, because it forces you to enumerate an exponentially growing number of them. If you are strictly constrained to use DFS, perhaps you are also allowed its "iterative deepening" version. That is, start by running a DFS limited by depth 1. If you do not find the target node v, you run a DFS limited by depth 2, etc, until you finally reach v at one of the depths. In this case you do not need to collect all the paths and may simply output the first one you find. Although it may seem "slow" as described, it is way faster than a blind full enumeration of all paths in the graph which you are currently doing.

Community
  • 1
  • 1
KT.
  • 10,815
  • 4
  • 47
  • 71
  • Thank you for your reply! I will try what you have suggested. I kind of have an idea on how to implement the things you said, but how would you exit the function if `changesSoFar` exceed the allowed switches though? – Pengibaby Dec 05 '18 at 01:55
  • the OP doesn't need a shortest path – mangusta Dec 05 '18 at 02:03
  • I actually remember seeing the word "shortest" in the question. Perhaps it was a hallucination. Anyway, finding the shortest path is still the right approach here, as it makes for a potentially shorter enumeration. Shorter paths tend to have less edge switches as well (the problem of finding the path with the least number of label changes *is*, in fact, a shortest path problem). – KT. Dec 05 '18 at 15:46
  • @Pengibaby simply add a `changesSoFar` input argument to your `pathDepthFirstSearch` function, update it appropriately in the code, and do `if (changesSoFar > MAX_CHANGES) return;` on the very first line. – KT. Dec 05 '18 at 16:22