2

Preface

In the previous article, we expressed the graph through the adjacency list array. This data structure will be the basis for us to realize the graph algorithm. In this article, we will start to learn the first search algorithm of the graph algorithm-depth first search.

Search API definition

public class Search {
    Search(Graph graph, int s);

    boolean marked(int v);
    
    int count();
}

Before starting to implement the algorithm, we still define the search API

  1. The construction method provides a graph object and a starting point s. All vertices connected to s need to be found
  2. marked to determine whether the vertices s and v are adjacent
  3. count returns the total number of vertices connected to vertex s

Depth first search

If the above picture is a maze, we need to find a way out from vertex 0. Assuming we have an infinitely long rope and a piece of chalk, we can consider finding the way like this:

  1. Choose a passage first and put a rope on the way
  2. Mark each intersection with a pen and continue to choose an untraveled passage
  3. When encountering an intersection that has been marked, return to the previous intersection and continue to choose an untraveled passage
  4. When there is no way to go at the retreat intersection, continue to retreat

In this way, the rope can always help you find a way out, and the mark will not let you repeat the passage you have already walked.

The idea of implementing depth-first search is the same as that of walking the maze;

public class DepthFirstSearch {
    private boolean marked[]; 
    private int count;

    public DepthFirstSearch(Graph graph, int s) {
        this.marked = new boolean[graph.V()];
        this.dfs(graph, s);
    }

    private void dfs(Graph graph, int v) {
        marked[v] = true;
        count++;
        for (int w : graph.adj(v)) {
            if (!marked[w]) {
                dfs(graph, w);
            }
        }
    }

    @Override
    public boolean marked(int v) {
        return marked[v];
    }

    @Override
    public int count() {
        return count;
    }
}

When searching for a graph, use recursion to traverse all vertices. When visiting one of the vertices:

  1. Mark it has been visited
  2. Recursively visit all adjacent points connected to it

unit test:
Build the following picture, and then test the depth first search

@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); //为了展示

    SeDepthFirstSearcharch search = new DepthFirstSearch(graph, 0);
    System.out.println(search.count());
    System.out.println(search.marked(6));
    System.out.println(search.marked(7));
    System.out.println(search.marked(2));
    System.out.println(search.marked(5));
}

Path finding API

The above recursive algorithm is just the beginning. From the above results, we can see that we can only determine which vertices are connected to the starting point s, and cannot give a specific path; in other words, we need to realize from the vertex s Whether there is a reachable path to vertex v, if it exists, please print it out

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

Find the reachable path in the graph based on depth-first search

We are still based on this picture. Since we need to find the reachable path, we need to record the edges in the figure when searching. Here we use an array edgeTo[]. If there is an edge, it is v->w, then it can be expressed as edgeTo[w]=v. After the deep search is completed, the edgeTo[] array is a tree represented by the parent chain
(The parent chain tree was also used in the previous article "How to detect whether two people in a social network are friends (union-find algorithm)" )

public class DepthFirstPaths {
    private boolean marked[];
    private int[] edgeTo;
    private int s;

    DepthFirstPaths(Graph graph, int s) {
        this.s = s;
        this.marked = new boolean[graph.V()];
        this.edgeTo = new int[graph.V()];
        this.dfs(graph, s);
    }

    private void dfs(Graph graph, int v) {
        this.marked[v] = true;
        for (int w : graph.adj(v)) {
            if (!marked[w]) {
                this.edgeTo[w] = v;
                this.dfs(graph, w);
            }
        }
    }

    public boolean hasPathTo(int v) {
        return 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;
    }
}

Draw a picture to track the trajectory of the depth-first search in detail, record the change of edgeTo and the gradual formation of the parent chain tree

Finally, the parent chain tree is formed. Next, we will write the unit test to verify whether the generated parent chain tree is consistent with the actual running result.

@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);

    DepthFirstPaths paths = new DepthFirstPaths(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);


}

The verification result exactly matches the parent chain tree


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, is not easy to write, please don’t use me , I hope my friends can follow , because these are all the power sources for my sharing🙏

sorting out programmer must read list: 16084c621e6fd1 https://github.com/silently9527/ProgrammerBooks

WeChat public account: Beta learns Java


Herman
622 声望3.7k 粉丝

知识星球:Herman's Notes