头图
sorting out the programmer must read list: 1609b51af38542 https://github.com/silently9527/ProgrammerBooks

WeChat public account: Beta learns Java

Preface

In the previous two articles, we can find a path from vertex v to vertex w in the graph through depth-first search, but depth-first search has a great relationship with vertex input, and the path found is not necessarily the shortest , Under normal circumstances, we often need to find the shortest path in the map, such as: map function. Here we need to use the breadth first search algorithm

Breadth first search

Still use the previously defined path-finding API

public class Paths {
    Paths(Graph graph, int s);
    
    boolean hasPathTo(int v); //判断出从s->v是否存在路径
    
    Iterable<Integer> pathTo(int v); //如果存在路径,返回路径
}

In breadth-first search, in order to find the shortest path, we need to traverse all the vertices in the order of the starting point, instead of using recursion; the idea of the algorithm:

  1. Use a queue to store vertices that have been marked but the adjacency list has not been traversed
  2. Take the next vertex v in the queue and mark it
  3. Add all unmarked vertices adjacent to v to the queue

In this algorithm, in order to save the path, we still need to use an edge array edgeTo[], and use a parent chain tree to represent the shortest path from the root node to all connected vertices.

public class BreadthFirstPaths {
    private boolean marked[];
    private int[] edgeTo;
    private int s;
    private Queue<Integer> queue = new LinkedListQueue<>();

    public BreadthFirstPaths(Graph graph, int s) {
        this.s = s;
        this.marked = new boolean[graph.V()];
        this.edgeTo = new int[graph.V()];

        bfs(graph, s);
    }

    private void bfs(Graph graph, int s) {
        this.marked[s] = true;
        this.queue.enqueue(s);
        while (!this.queue.isEmpty()) {
            Integer v = this.queue.dequeue();
            for (int w : graph.adj(v)) {
                if (!this.marked[w]) {
                    this.marked[w] = true;
                    this.edgeTo[w] = v;
                    this.queue.enqueue(w);
                }
            }
        }


    }

    public boolean hasPathTo(int v) {
        return this.marked[v];
    }

    public Iterable<Integer> pathTo(int v) {
        if (!hasPathTo(v)) {
            throw new IllegalStateException("s不能到达v");
        }
        Stack<Integer> stack = new LinkedListStack<>();
        stack.push(v);
        while (edgeTo[v] != s) {
            stack.push(edgeTo[v]);
            v = edgeTo[v];
        }
        stack.push(s);
        return stack;
    }
}

The following figure is a column, let’s take a look at the trajectory of breadth first search

Unit test code:

@Test
public void test() {
    Graph graph = new Graph(8);
    graph.addEdge(0, 1);
    graph.addEdge(0, 2);
    graph.addEdge(0, 5);
    graph.addEdge(1, 3);
    graph.addEdge(2, 4);
    graph.addEdge(4, 3);
    graph.addEdge(5, 3);
    graph.addEdge(6, 7);

    BreadthFirstPaths paths = new BreadthFirstPaths(graph, 0);
    System.out.println(paths.hasPathTo(5));
    System.out.println(paths.hasPathTo(2));
    System.out.println(paths.hasPathTo(6));

    paths.pathTo(5).forEach(System.out::print);
    System.out.println();
    paths.pathTo(4).forEach(System.out::print);
    System.out.println();
    paths.pathTo(2).forEach(System.out::print);
    System.out.println();
    paths.pathTo(3).forEach(System.out::print);

}

The result of the execution is consistent with the running track we analyzed

Symbol map

The graph algorithms learned in recent articles all use numbers as vertices, because numbers are very simple and convenient to implement these algorithms, but in actual scenes, characters are usually used as vertices instead of numbers. For example: the location on the map, the relationship between the movie and the actors.

In order to meet the actual scenario, we only need to do a mapping between the number and the string. At this time, we will think of the map implemented in the previous article (map implemented by binary tree, map implemented by red-black tree, map implemented by hash table, etc., Interested students can go to search), use Map to maintain the mapping relationship between strings and numbers.

public interface SymbolGraph {
    boolean contains(String key); //判断是否存在顶点

    int index(String key); //通过名称返回对应的数字顶点

    String name(int v); //通过数字顶点返回对应的字符名称

    Graph graph();
}

Ideas to realize:

  1. Use Map to save the string-number mapping, the key is a string, and the value is a number
  2. Use an array to reversely map numbers-strings, the subscripts of the array correspond to the digital vertices, and the values correspond to the string names

Assuming that the vertices and edges of the construction graph are represented by character strings, such as: a,b,c,d\nb,a,h,l,p\ng,s,z Use \n to separate the first character string in each paragraph to represent the vertex v, and the following represent the adjacent vertices connected to the vertex v;

The actual process can be determined according to the specific situation, and it does not have to be this kind of string. It can come from a database or a request from the Internet.

The code is implemented as follows:

public class SymbolGraph {
    private Map<String, Integer> map = new RedBlack23RedBlackTreeMap<>();
    private String[] keys;
    private Graph graph;

    public SymbolGraph(String text) {
        Arrays.stream(text.split("\n")).forEach(line -> {
            String[] split = line.split(",");
            for (int i = 0; i < split.length; i++) {
                map.put(split[i], i);
            }
        });

        this.keys = new String[map.size()];
        map.keys().forEach(key -> {
            this.keys[this.map.get(key)] = key;
        });

        this.graph = new Graph(this.keys.length);
        Arrays.stream(text.split("\n")).forEach(line -> {
            String[] split = line.split(",");
            int v = this.map.get(split[0]);
            for (int i = 1; i < split.length; i++) {
                this.graph.addEdge(v, this.map.get(split[i]));
            }
        });
        
    }

    public boolean contains(String key) {
        return map.contains(key);
    }

    public int index(String key) {
        return map.get(key);
    }

    public String name(int index) {
        return this.keys[index];
    }

    public Graph graph() {
        return this.graph;
    }

    public static void main(String[] args) {
        System.out.println(Arrays.toString("323\n2323".split("\n")));
    }
}

All the source code in the article has been placed in the github warehouse:
https://github.com/silently9527/JavaCore

Finally (point attention, don’t get lost)

There may be more or less deficiencies and mistakes in the article. If you have suggestions or comments, you are welcome to comment and exchange.

Finally, writing is not easy, please don’t use me , I hope friends can like and comment and follow three consecutive, because these are all the sources of motivation for my sharing🙏


Herman
622 声望3.7k 粉丝

知识星球:Herman's Notes