简体   繁体   中英

Find shortest path between two locations (Unweighted & Using BFS)

SHORTEST PATH USING BFS

public static LinkedList<String> findShortestPath(String start, String end) {

    LinkedList<String> bfsList = new LinkedList<String>();
    Queue<Actor> queue = new LinkedList<Actor>();
    Map<String, Actor> prev = new HashMap<String, Actor>();
    Actor current = graph.getActorsByName().get(start);

    queue.add(current);
    current.setVisited(true);

    while(!queue.isEmpty()) {

        current = queue.remove();;

        if(current.getName().equals(end)) {

            break;

        } else {

            for(int i = 0; i < current.getFriends().size(); i++) {

                if(current.getFriends().get(i).getVisited() == false) {

                    queue.add(graph.getActorsByName().get(current.getFriends().get(i).getName()));
                    graph.getActorsByName().get(current.getFriends().get(i).getName()).setVisited(true);
                    prev.put(current.getFriends().get(i).getName(), current);

                }

            }

        }

    }

    if(!current.getName().equals(end)) {

        System.out.println("\nThere is no path between " + start + " and " + end);

    }

    for(Map.Entry<String, Actor> entry : prev.entrySet()) {

        String key = entry.getKey();
        bfsList.add(key);

    }

    return bfsList;

}

Above is the code I'm using to try and find the shortest path between two points in a graph. It isn't giving me the correct path between the two points, and I cannot figure out why.

I've set up a test rig, where I expect an actor to have a name and some friends. Those immediate friends also have a reciprocal relationship.

In this test I am looking for the shortest path between "james" and "mary".

import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import org.junit.Test;

public class ShortestPathTest {

    Map<String, ShortestPath.Actor> graph = new HashMap<>();

    private ShortestPath.Actor newActor(String name, String... friends) {
        ShortestPath.Actor actor = graph.computeIfAbsent(name, k -> new ShortestPath.Actor(name));
        for(String friendsName : friends) {
            ShortestPath.Actor friend = newActor(friendsName);
            actor.addFriend(friend);
            friend.addFriend(actor);
        }
        return actor;
    }

    @Test
    public void findShortestPath() {
        newActor("james", "harry", "luke", "john");
        newActor("harry", "luke", "mary");

        LinkedList<String> shortestPath = ShortestPath.findShortestPath(graph, "james", "mary");
        assertThat(shortestPath, equalTo(Arrays.asList("james", "harry", "mary")));
    }
}

The result I received was not consistent with my expectations and for some reason "mary" is in the middle of the output:

java.lang.AssertionError: 
Expected: <[james, harry, mary]>
     but: was <[luke, harry, mary, john]>

Comparing the algorithm you have referenced , I suspect the issue is in this part (of the referenced algorithm):

for(Node node = finish; node != null; node = prev.get(node)) {
    directions.add(node);
}
directions.reverse();

In your implementation you have initialised prev = new HashMap<String, Actor>() and you are then adding every visited node (not just the nodes to indicate the shortest path). You need to use prev as sort of linked list... for example:

    for (Actor node = graph.get(end); node != null; node = prev.get(node.getName())) {
        bfsList.add(node.getName());
    }
    Collections.reverse(bfsList);

Full updated code for testing purposes

shortest path implementation

Only minor edits applied to original posting:

  • Input graph as parameter
  • Simplified logic in the inner loop by extracting looked at variable
  • Returns empty list if no path can be found
  • Fixed loop of hash map that analysed the shortest path found
  • Added Actor class for testing

code listing

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

public class ShortestPath {

    public static List<String> findShortestPath(Map<String, Actor> graph, String start,
        String end) {

        LinkedList<String> bfsList = new LinkedList<>();
        Queue<Actor> queue = new LinkedList<>();
        Map<String, Actor> prev = new HashMap<>();
        Actor current = graph.get(start);

        queue.add(current);
        current.setVisited(true);

        while (!queue.isEmpty()) {

            current = queue.remove();

            if (current.getName().equals(end)) {
                break;
            } else {
                LinkedList<Actor> currentFriends = current.getFriends();
                for (Actor currentFriend : currentFriends) {
                    if (!currentFriend.getVisited()) {
                        queue.add(currentFriend);
                        currentFriend.setVisited(true);
                        prev.put(currentFriend.getName(), current);
                    }
                }
            }
        }

        if (!current.getName().equals(end)) {
            System.out.println("\nThere is no path between " + start + " and " + end);
            return Collections.emptyList();
        }
        for (Actor node = graph.get(end); node != null; node = prev.get(node.getName())) {
            bfsList.add(node.getName());
        }
        Collections.reverse(bfsList);

        return bfsList;

    }

    static class Actor {

        private final String name;
        private final LinkedList<Actor> friends = new LinkedList<>();

        private boolean visited;

        Actor(String name) {
            this.name = name;
        }

        public void setVisited(boolean visited) {
            this.visited = visited;
        }

        // Would normally be `isVisited`
        public boolean getVisited() {
            return visited;
        }

        public String getName() {
            return name;
        }

        public LinkedList<Actor> getFriends() {
            return friends;
        }

        public void addFriend(Actor actor) {
            this.friends.add(actor);
        }
    }
}

Test code

Builds test graph and asserts the correct path is found between various nodes

import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;

public class ShortestPathTest {

    Map<String, ShortestPath.Actor> graph = new HashMap<>();

    @Before
    public void setup() {
        newActor("james", "harry", "luke", "john");
        newActor("harry", "luke", "mary");
        newActor("luke", "john", "hepzibah");
        newActor("john", "kate");
        newActor("mary", "hepzibah", "mia");
        newActor("hepzibah", "richard");
        newActor("kate", "martin", "mia");
        newActor("mia", "susan");
        newActor("richard", "rebecca");
        newActor("rebecca", "hannah");
        newActor("michelle");
    }

    private ShortestPath.Actor newActor(String name, String... friends) {
        ShortestPath.Actor actor = graph.computeIfAbsent(name, k -> new ShortestPath.Actor(name));
        for (String friendsName : friends) {
            ShortestPath.Actor friend = newActor(friendsName);
            actor.addFriend(friend);
            friend.addFriend(actor);
        }
        return actor;
    }

    @Test
    public void findShortestPath() {
        List<String> shortestPath = ShortestPath.findShortestPath(graph, "james", "mary");
        assertThat(shortestPath, equalTo(Arrays.asList("james", "harry", "mary")));
    }

    @Test
    public void findLongerShortestPath() {
        List<String> shortestPath = ShortestPath.findShortestPath(graph, "james", "mia");
        assertThat(shortestPath, equalTo(Arrays.asList("james", "harry", "mary", "mia")));
    }

    @Test
    public void findAnotherShortestPath() {
        List<String> shortestPath = ShortestPath.findShortestPath(graph, "harry", "hannah");
        assertThat(shortestPath, equalTo(Arrays.asList("harry", "luke", "hepzibah", "richard", "rebecca", "hannah")));
    }

    @Test
    public void findNoPath() {
        List<String> shortestPath = ShortestPath.findShortestPath(graph, "james", "michelle");
        assertThat(shortestPath, equalTo(Collections.emptyList()));
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM