【数据结构与算法】无向图的结构与遍历 BFS&DFS

1 表示无向图的数据类型

1.1 邻接矩阵

可以使用一个V*V的二维布尔矩阵,当定点v和定点w相连的时候,定义第v行第w列的值为true,否则为false。邻接矩阵不适合定点较多的情况,含有百万的顶点数的图是很常见的,V^2的空间很难得满足。

1.2 邻接表

邻接表使用一个记录当前顶点相邻顶点的链表即边表,和一个快速访问给定顶点的顶点列表即顶点表。数据类型如下:

public class Graph {
    private final int V;
    private int E;
    private Bag<Integer>[] adj;

    Graph(int V) {
        this.V = V;
        adj = (Bag<Integer>[]) new Bag[V];
        for (int i = 0; i < V; i++)
            adj[i] = new Bag<Integer>();
    }

    public void addEdge(int u, int v) {
        adj[u].add(v);
    }

    public int V() {
        return this.V;
    }

    public int E() {
        return this.E;
    }

    public Iterable<Integer> adj(int v) {
        return adj[v];
    }

    public String toString() {
        String s = V + " vertices, " + E + " edges.\n";
        for (int i = 0; i < V; i++) {
            s += i + " : ";
            for(int w : this.adj(i))
                s += w + " ";
            s += "\n";
        }

        return s;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int V = sc.nextInt();
        int E = sc.nextInt();
        Graph g = new Graph(V);
        for (int i = 0; i < E; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            g.addEdge(u, v);
            g.addEdge(v, u);
        }
        System.out.println(g);
    }
}

测试输入:

13
13
0 5
4 3
0 1
9 12
6 4
5 4
0 2
11 12
9 10
0 6
7 8
9 11
5 3

输出结果

13 vertices, 0 edges.
0 : 6 2 1 5 
1 : 0 
2 : 0 
3 : 5 4 
4 : 5 6 3 
5 : 3 4 0 
6 : 0 4 
7 : 8 
8 : 7 
9 : 11 10 12 
10 : 9 
11 : 9 12 
12 : 11 9 

2 深度优先搜索

DepthFirstSearch.class

2.1 DFS的递归写法

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

    DepthFirstSearch(Graph g, int s) {
        marked = new boolean[g.V()];
        dfs(g, s);
    }
    void visit(int v) {
        System.out.print(v + " ");
    }
    void dfs(Graph g, int v) {
        marked[v] = true;
        visit(v);
        for(int w : g.adj(v)) {
            if(!marked[w]) {
                dfs(g, w);
            }
        }
    }
}

测试结果

0 6 4 5 3 2 1

2.2 ★DFS的非递归写法

非递归写法主要借助了栈,思想与二叉树的先序遍历类似:每次将栈顶出栈再将栈顶的右节点和左节点依次入栈,只不过这里需要每次入栈一个相邻未访问节点,等到全被访问才将栈顶出栈

①首先将第一个顶点入栈,并标记为已访问。

②遍历栈顶顶点第一个未被访问过的相邻节点将其入栈,如果栈顶顶点没有未被访问过的相邻顶点则将栈顶节点出栈。

③重复执行②直至栈空。

    void dfs2(Graph g, int v) {
        Stack<Integer> s = new Stack<>();
        s.push(v);
        visit(v);
        marked[v] = true;
        while(!s.empty()) {
            int x = s.peek();

            boolean flag = false;
            // 每次访问第一个未被访问的相邻节点
            for(int w : g.adj(x)) {
                if(!marked[w]) {
                    visit(w);
                    s.push(w);
                    marked[w] = true;
                    flag = true;
                    break;
                }
            }
            // 如果不存在相邻顶点则将栈顶顶点出栈
            if(!flag)
                s.pop();
        }
    }

2.3 寻找路径

2.4 DFS寻找所有路径

3 广度优先搜索

广度优先遍历一般用于求单源最短路径。

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

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

        bfs(g, s);
    }

    public void bfs(Graph g, int v) {
        Queue<Integer> q = new LinkedBlockingQueue<>();
        q.add(v);
        marked[v] = true;

        while (!q.isEmpty()) {
            int x = q.poll();
            for (int w : g.adj(x)) {
                if (!marked[w]) {
                    edgeTo[w] = x;
                    q.add(w);
                    marked[w] = true;
                }
            }
        }
    }

    public Stack<Integer> pathTo(int v) {
        Stack<Integer> s = new Stack<>();
        while (v != this.s) {
            s.push(v);
            v = edgeTo[v];
        }
        s.push(v);
        return s;
    }
}
posted @ 2022-08-01 14:43  Tod4  阅读(74)  评论(0编辑  收藏  举报