Home > Back-end >  How to find a path in a matrix which must pass through some cells
How to find a path in a matrix which must pass through some cells

Time:12-02

Given a 2d matrix, I have a start cell, end cell, cells that must be visited and cells that cannot be visited.

What would be the optimal way to find a path that:

  • starts at the start cell
  • ends at the end cell
  • passes through all must visit cell
  • does not pass through any cell that cannot be visited
  • does not pass any cell twice
  • we can only move left, right, up and down

The matrix size can be at most 10x10.

For example:

S - start
E - end
0 - can visit
X - can not visit
M - must visit

S 0 0 M 0
0 0 0 X 0
0 M 0 0 0
0 X 0 0 0
0 0 0 0 E


One solution would be:

* 0 * * *
* 0 * X *
* * * 0 *
0 X 0 0 *
0 0 0 0 *

The path doesn't necessarily should be the shortest one, but it would be nice if it can be, and it can be calculated quiet fast.

CodePudding user response:

Essentially, you need to calculate the minimum spanning tree ( MST, google it ).

Start with the cells that must be visited. These will be the nodes in the graph you apply the MST algorithm to. Find the lengths of the shortest paths between each pair of nodes ( in such a small problem you can easily do that manually ) These will be the links, and link costs you input to the MST.

Input the constructed graph to the MST method in your favorite graph-theory library.

For a more complicated version of this problem, which is solved using the same technique, see How to find a shortest path in a graph that while travelling it, you can "see" all the nodes within a radius

CodePudding user response:

You can model the grid with a class Grid that would have a width and a height, and a set of must locations a set of mustNot locations:

public class Grid {
    private final int width;
    private final int height;
    private final Set<Location> must = new HashSet<>();
    private final Set<Location> mustNot = new HashSet<>();

    public Grid(int width, int height,
            Collection<Location> must, Collection<Location> mustNot) {
        this.width = width;
        this.height = height;
        this.must.addAll(must);
        this.mustNot.addAll(mustNot);
    }

In that class, you would have a method solve that would take a start location and an end location, and would return the best path:

public PathNode solve(Location start, Location end) {
    ...
}

class Location looks like this:

public class Location {
    public final int row;
    public final int col;

    public Location(int row, int col) {
        this.row = row;
        this.col = col;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 41 * hash   this.row;
        hash = 41 * hash   this.col;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Location other = (Location) obj;
        if (this.row != other.row) {
            return false;
        }
        return this.col == other.col;
    }

    @Override
    public String toString() {
        return "("   row   ", "   col   ')';
    }
}

Basically, it's a row and a col.

A path is a linked list of PathNodes:

public class PathNode {
    public final Location loc;
    public final PathNode link;

    public PathNode(Location loc, PathNode link) {
        this.loc = loc;
        this.link = link;
    }

    public boolean contains(Location loc) {
        PathNode n = this;
        while (n != null) {
            if (n.loc.equals(loc)) {
                return true;
            }
            n = n.link;
        }
        return false;
    }
}

I've added a contains method because of the condition that a path must not go through the same location twice.

Now, the algorithm is a breadth-first search:

public PathNode solve(Location start, Location end) {
    List<PathNode> paths = new ArrayList<>();
    if (validLoc(start.row, start.col) == null) {
        return null;
    }
    paths.add(new PathNode(start, null));
    for (int i = 0; i < paths.size();   i) {
        PathNode path = paths.get(i);
        Location loc = path.loc;
        if (loc.equals(end)) {
            // at the end point, we must still check the path goes through
            // all the 'must' cells
            if (allMustInPath(path)) {
                // found a path
                return path;
            }
        } else {
            addToPaths(path, left(loc), paths);
            addToPaths(path, right(loc), paths);
            addToPaths(path, up(loc), paths);
            addToPaths(path, down(loc), paths);
        }
    }
    return null;
}

We still need a few more methods:

private Location left(Location loc) {
    return validLoc(loc.row, loc.col-1);
}

private Location right(Location loc) {
    return validLoc(loc.row, loc.col 1);
}

private Location up(Location loc) {
    return validLoc(loc.row-1, loc.col);
}

private Location down(Location loc) {
    return validLoc(loc.row 1, loc.col);
}

private Location validLoc(int row, int col) {
    if (row >= 0 && row < height && col >= 0 && col < width) {
        Location loc = new Location(row, col);
        return mustNot.contains(loc) ? null : loc;
    }
    return null;
}

private boolean allMustInPath(PathNode path) {
    for (Location loc: must) {
        if (!path.contains(loc)) {
            return false;
        }
    }
    return true;
}

private void addToPaths(PathNode path, Location loc, List<PathNode> paths) {
    if (loc == null) {
        return;
    }
    loc = validLoc(loc.row, loc.col);
    if (loc != null && !path.contains(loc)) {
        paths.add(new PathNode(loc, path));
    }
}

The path returned by the solve method, is actually in backward order. For your example above, it finds the path that you mentionned:

    Grid grid = new Grid(5,5,
            Arrays.asList(
                    new Location(0,3),
                    new Location(2,1)),
            Arrays.asList(
                    new Location(1,3),
                    new Location(3,1)));
    PathNode path = grid.solve(
            new Location(0,0),
            new Location(4,4));
    if (path == null) {
        System.out.println("No solution found");
    }
    while (path != null) {
        System.out.println(path.loc);
        path = path.link;
    }


(4, 4)
(3, 4)
(2, 4)
(1, 4)
(0, 4)
(0, 3)
(0, 2)
(1, 2)
(2, 2)
(2, 1)
(1, 1)
(0, 1)
(0, 0)
  • Related