无向图

无向图

图模型

四种重要的图模型:

  • 无向图
  • 有向图
  • 加权图
  • 加权有向图

无向图:边仅仅是两个顶点之间的连接。

术语表

特殊的情况:

  • 自环
  • 平行边

术语表:

  • 相邻
  • 度数
  • 子图
  • 路径:由边顺序连接的一系列顶点
  • 路径的长度:其中所包含的边数
  • 简单路径:一条没有重复顶点的路径
  • 连通
  • 连通图
  • 极大连通子图
  • 生成树
  • 稀疏图/稠密图/二分图

表示无向图的数据模型

无向图 API:

public class Graph {
    Graph(int V); // 创建一个含有 V 个顶点但不含有边的图
    Graph(In in); // 从标准输入流 in 读取一幅图
    int V(); // 顶点数
    int E(); // 边数
    void addEdge(int v, int w); // 添加一个边 v-w
    Iterable<Integer> adj(int v); // 和 v 相邻的所有顶点
    String toSting(); // 图的字符串表示
}

本节将学习的所有算法都基于 adj() 方法所抽象的基本操作

实现图 API 的三种数据结构:

  • 邻接矩阵:用 V 乘 V 的布尔矩阵表示,顶点存在连接时值为 true,否则为 false(空间消耗大)
  • 边的数组:使用一个 Edge 对象表示边(实现 adj() 需要检查图中所有边)
  • 邻接表数组:使用一个以顶点为索引的列表数组,列表保存该顶点的相邻顶点(本章选择的数据结构)

邻接表数组数据结构:

V    相邻顶点(邻接表)
0 -> 1, 2
1 -> 0
2 -> 0
3 -> 4
4 -> 3
// 无向图
public class Graph {
    private int V; // 顶点数
    private int E; // 边数
    private Bag<Integer>[] adj; // 邻接表数组

    // 创建一个包含 V 个顶点,但不含有边的图
    public Graph(int V) {
        this.V = V;
        this.E = 0;
        this.adj = new Bag[V];
        for (int i = 0; i < V; i++) {
            adj[i] = new Bag<>();
        }
    }

    // 从标准输入流读取一幅图
    public Graph(In in) {
        this(in.readInt());
        int E = in.readInt(); // 局部变量
        for (int i = 0; i < V; i++) {
            int v = in.readInt();
            int w = in.readInt();
            addEdge(v, w);
        }
    }

    // 添加一条边 v-w
    public void addEdge(int v, int w) {
        adj[v].add(w);
        adj[w].add(v);
        E++;
    }

    // 顶点 v 的所有相邻顶点
    public Iterable<Integer> adj(int v) {
        return adj[v];
    }

    public int V() {
        return V;
    }

    public int E() {
        return E;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(String.format("%d vertices, %d edges%n", V, E));
        for (int v = 0; v < V; v++) {
            builder.append(v + ": ");
            for (int w : adj(v)) {
                builder.append(w +" ");
            }
            builder.append(System.lineSeparator());
        }
        return builder.toString();
    }
}

图处理算法设计模式

为每个任务(算法)创建一个相应的类,用例可以创建相应的对象来完成任务。

用例:

  • 创建一幅图
  • 将图传递给实现某个算法的类
  • 调用实现算法的类对象完成任务

图搜索算法

搜索迷宫:检查入口和出口是否连通

图搜索算法 API:

public class Search {
    Search(Graph G, int s); // 找到和起点 s 连通的所有顶点
    boolean marked(int v); // v 和 s 是否连通
    int count(); // 与 s 连通的顶点总数
}

深度优先搜索:

// 深度优先搜索
// 在访问一个顶点时:
// * 将它标记为已访问
// * 递归地访问它的所有没有被标记过的邻居顶点
public class DepthFirstSearch implements Search {
    private boolean[] marked;
    private int count;

    // s:起点
    public DepthFirstSearch(Graph G, int s) {
        marked = new boolean[G.V()];
        count = 0;
        dfs(G, s);
    }

    private void dfs(Graph G, int v) {
        marked[v] = true;
        count++;
        for (int w : G.adj(v)) {
            if (!marked[w]) {
                dfs(G, w);
            }
        }
    }
    
    @Override
    public boolean marked(int v) {
        return marked[v];
    }

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

单点路径

单点路径 API:

public class Paths {
    Paths(Graph G, int s); // 在 G 中找出所有起点为 s 的路径
    boolean hasPathTo(int v); // 是否存在从 s 到 v 的路径
    Iterable<Integer> pathTo(int v); // s 到 v 的路径,如果不存在则返回 null
}

深度优先搜索:

// 使用深度优先搜索解决单点路径问题
public class DepthFirstPaths implements Paths {
    private boolean[] marked;
    private int[] pathTo;
    private int s;

    // s: 起点
    public DepthFirstPaths(Graph G, int s) {
        marked = new boolean[G.V()];
        pathTo = new int[G.V()];
        this.s = s;
        dfs(G, s);
    }

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

    // 是否存在 s 到 v 的路径
    @Override
    public boolean hasPathTo(int v) {
        return marked[v];
    }

    // 返回 s 到 v 的路径,如果不存在返回 null
    @Override
    public Iterable<Integer> pathTo(int v) {
        if (!hasPathTo(v)) {
            return null;
        }
        Stack<Integer> stack = new Stack<>();
        while (v != s) {
            stack.push(v);
            v = pathTo[v];
        }
        stack.push(s);
        return stack;
    }
}

广度优先搜索(单点最短路径):

public class BreadthFirstPaths implements Paths {
    private boolean[] marked;
    private int[] pathTo;
    private int s;

    public BreadthFirstPaths(Graph G, int s) {
        marked = new boolean[G.V()];
        pathTo = new int[G.V()];
        this.s = s;
        bfs(G, s);
    }

    private void bfs(Graph G, int s) {
        Queue<Integer> queue = new Queue<>();
        queue.add(s);
        marked[s] = true;
        while (!queue.isEmpty()) {
            int v = queue.remove();
            for (int w : G.adj(v)) {
                if (!marked[w]) {
                    pathTo[w] = v;
                    queue.add(w);
                    marked[w] = true;
                }
            }
        }
    }

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

    @Override
    public Iterable<Integer> pathTo(int v) {
        if (!hasPathTo(v)) {
            return null;
        }
        Stack<Integer> stack = new Stack<>();
        while (v != s) {
            stack.push(v);
            v = pathTo[v];
        }
        stack.push(s);
        return stack;
    }
}

连通分量

很多用例都需要能够独立地处理每个连通分量。

连通分量 API:

public class CC {
    CC(Graph G); // 预处理构造函数
    boolean connected(int v, int w); // v 和 w 连通吗
    int count(); // 连通分量数量
    int id(int v); // v 所在的连通分量的标识符 (0 - count() - 1)
}

深度优先搜索:

public class CC {
    private boolean[] marked;
    private int[] ids;
    private int count;

    public CC(Graph G) {
        marked = new boolean[G.V()];
        ids = new int[G.V()];
        count = 0;
        for (int v = 0; v < G.V(); v++) {
            if (!marked[v]) {
                dfs(G, v, count);
                count++;
            }
        }
    }

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

    public boolean connected(int v, int w) {
        return ids[v] == ids[w];
    }

    public int count() {
        return count;
    }

    public int id(int v) {
        return ids[v];
    }
}
posted @ 2022-10-01 22:00  廖子博  阅读(77)  评论(0编辑  收藏  举报