图
图
常见类型与术语
图的表示
邻接矩阵
邻接表
基础操作
基于邻接矩阵的实现
基于邻接表的实现
遍历
广度优先
深度优先
图
图 是一种非线性数据结构,由 顶点 和 边 组成。
相较于线性关系的链表和分治关系的树,网络关系的图自由度更高
常见类型与术语
根据边是否具有方向,可分为
无向图:
有向图:
根据所有顶点是否连通,可分为
连通图:
非连通图:
根据是否 为边添加“权重”变量,可分为
无权图:
有权图:
常用术语:
邻接:当两顶点之间存在边相连时,称这两顶点“邻接”。
路径:从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。
度 :一个顶点拥有的边数。对于有向图, 入度 表示有多少条边指向该顶点, 出度
表示有多少条边从该顶点指出。
图的表示
邻接矩阵
这个图的邻接矩阵为:
邻接表
这个图的邻接表为:
基础操作
基于邻接矩阵的实现
import java.util.ArrayList;
import java.util.List;/* 基于邻接矩阵实现的无向图类 */
class GraphAdjMat {private List<Integer> vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引”private int[][] adjMat; // 邻接矩阵,行列索引对应“顶点索引”/* 构造方法 */public GraphAdjMat(int[] vertices, int[][] edges) {this.vertices = new ArrayList<>();this.adjMat = new int[vertices.length][vertices.length]; // 初始化邻接矩阵// 添加顶点for (int val : vertices) {addVertex(val);}// 添加边for (int[] e : edges) {addEdge(e[0], e[1]);}}/* 获取顶点数量 */public int size() {return vertices.size();}/* 添加顶点 */public void addVertex(int val) {vertices.add(val);// 扩展邻接矩阵int n = size();int[][] newAdjMat = new int[n][n];// 复制原有邻接矩阵的内容for (int i = 0; i < n - 1; i++) {System.arraycopy(adjMat[i], 0, newAdjMat[i], 0, n - 1);}// 设置新顶点的邻接关系为0for (int i = 0; i < n; i++) {newAdjMat[i][n - 1] = 0; // 新列newAdjMat[n - 1][i] = 0; // 新行}adjMat = newAdjMat; // 更新邻接矩阵引用}/* 删除顶点 */public void removeVertex(int index) {if (index >= size()) {throw new IndexOutOfBoundsException();}// 在顶点列表中移除索引 index 的顶点vertices.remove(index);int n = size();int[][] newAdjMat = new int[n - 1][n - 1];for (int i = 0, newRow = 0; i < n; i++) {if (i != index) {for (int j = 0, newCol = 0; j < n; j++) {if (j != index) {newAdjMat[newRow][newCol++] = adjMat[i][j]; // 复制不包含被删除顶点的行列}}newRow++;}}adjMat = newAdjMat; // 更新邻接矩阵引用}/* 添加边 */// 参数 i, j 对应 vertices 元素索引public void addEdge(int i, int j) {// 索引越界与相等处理if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {throw new IndexOutOfBoundsException();}// 在无向图中,邻接矩阵关于主对角线对称adjMat[i][j] = 1;adjMat[j][i] = 1;}/* 删除边 */// 参数 i, j 对应 vertices 元素索引public void removeEdge(int i, int j) {// 索引越界与相等处理if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {throw new IndexOutOfBoundsException();}adjMat[i][j] = 0;adjMat[j][i] = 0;}/* 打印邻接矩阵 */public void print() {System.out.print("顶点列表 = ");System.out.println(vertices);System.out.println("邻接矩阵 =");for (int[] row : adjMat) {for (int val : row) {System.out.print(val + " ");}System.out.println();}}
}
主要功能
- 添加顶点:增加新的顶点并扩展邻接矩阵。
- 删除顶点:移除指定索引的顶点和对应的邻接关系。
- 添加边:在邻接矩阵中设置两个顶点之间的边。
- 删除边:移除两个顶点之间的边。
- 打印邻接矩阵:以易于阅读的格式输出邻接矩阵。
基于邻接表的实现
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/* 顶点类 */
class Vertex {int val; // 顶点值public Vertex(int val) {this.val = val;}// 重写 equals 和 hashCode,方便在 Map 中使用。@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (!(obj instanceof Vertex)) return false;Vertex other = (Vertex) obj;return this.val == other.val;}@Overridepublic int hashCode() {return Integer.hashCode(val);}
}/* 基于邻接表实现的无向图类 */
class GraphAdjList {// 邻接表,key:顶点,value:该顶点的所有邻接顶点Map<Vertex, List<Vertex>> adjList;/* 构造方法 */public GraphAdjList(Vertex[][] edges) {this.adjList = new HashMap<>();// 添加所有顶点和边for (Vertex[] edge : edges) {addVertex(edge[0]);addVertex(edge[1]);addEdge(edge[0], edge[1]);}}/* 获取顶点数量 */public int size() {return adjList.size();}/* 添加边 */public void addEdge(Vertex vet1, Vertex vet2) {if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1.equals(vet2)) {throw new IllegalArgumentException();}// 添加边 vet1 - vet2adjList.get(vet1).add(vet2);adjList.get(vet2).add(vet1);}/* 删除边 */public void removeEdge(Vertex vet1, Vertex vet2) {if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1.equals(vet2)) {throw new IllegalArgumentException();}// 删除边 vet1 - vet2adjList.get(vet1).remove(vet2);adjList.get(vet2).remove(vet1);}/* 添加顶点 */public void addVertex(Vertex vet) {if (!adjList.containsKey(vet)) {// 在邻接表中添加一个新链表adjList.put(vet, new ArrayList<>());}}/* 删除顶点 */public void removeVertex(Vertex vet) {if (!adjList.containsKey(vet)) {throw new IllegalArgumentException();}// 在邻接表中删除顶点 vet 对应的链表adjList.remove(vet);// 遍历其他顶点的链表,删除所有包含 vet 的边for (List<Vertex> list : adjList.values()) {list.remove(vet);}}/* 打印邻接表 */public void print() {System.out.println("邻接表 =");for (Map.Entry<Vertex, List<Vertex>> pair : adjList.entrySet()) {List<Integer> tmp = new ArrayList<>();for (Vertex vertex : pair.getValue()) {tmp.add(vertex.val);}System.out.println(pair.getKey().val + ": " + tmp + ",");}}
}// 示例使用
class Main {public static void main(String[] args) {Vertex v1 = new Vertex(1);Vertex v2 = new Vertex(2);Vertex v3 = new Vertex(3);Vertex[][] edges = {{v1, v2}, {v2, v3}, {v1, v3}};GraphAdjList graph = new GraphAdjList(edges);graph.print();}
}
这个类实现了基于邻接表的无向图,包括了添加、删除顶点和边的功能,并且重写了 Vertex
类的 equals
和 hashCode
方法,以便在 HashMap
中正确使用。打印功能将输出每个顶点及其邻接顶点列表。
遍历
广度优先
广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,并一层层向外 扩张 。
/* 广度优先遍历 */
List<Integer> graphBFS(GraphAdjMat graph, int startIdx) {// 顶点遍历序列List<Integer> res = new ArrayList<>();// 哈希集,用于记录已被访问过的顶点Set<Integer> visited = new HashSet<>();visited.add(startIdx); // 将起始顶点标记为已访问// 队列用于实现 BFSQueue<Integer> que = new LinkedList<>();que.offer(startIdx); // 将起始顶点入队// 以顶点 startIdx 为起点,循环直至访问完所有顶点while (!que.isEmpty()) {int idx = que.poll(); // 队首顶点出队res.add(graph.vertices.get(idx)); // 记录访问顶点// 遍历该顶点的所有邻接顶点for (int j = 0; j < graph.size(); j++) {if (graph.adjMat[idx][j] == 1 && !visited.contains(j)) { // 判断是否相邻且未访问que.offer(j); // 只入队未访问的顶点visited.add(j); // 标记该顶点已被访问}}}// 返回顶点遍历序列return res;
}
主要功能
- 输入参数:接收一个
GraphAdjMat
对象和起始顶点的索引。 - 结果列表:使用一个列表
res
来记录访问的顶点。 - 访问记录:使用一个哈希集
visited
来记录已经访问过的顶点,避免重复访问。 - 队列实现BFS:使用
Queue
来按层次遍历图的顶点。
注意事项
- 确保在构造图时,已知起始顶点的索引。
- 广度优先遍历的结果将返回按照层次顺序访问的顶点,适合用于查找最短路径或层级关系等应用场合。
深度优先
深度优先遍历是一种优先走到底、无路可走再回头的遍历方式 。
/* 深度优先遍历辅助函数 */
void dfs(GraphAdjMat graph, Set<Integer> visited, List<Integer> res, int index) {res.add(graph.vertices.get(index)); // 记录访问顶点visited.add(index); // 标记该顶点已被访问// 遍历该顶点的所有邻接顶点for (int j = 0; j < graph.size(); j++) {if (graph.adjMat[index][j] == 1 && !visited.contains(j)) { // 判断是否相邻且未访问dfs(graph, visited, res, j); // 递归访问邻接顶点}}
}/* 深度优先遍历 */
// 使用邻接矩阵表示图,以便获取指定顶点的所有邻接顶点
List<Integer> graphDFS(GraphAdjMat graph, int startIdx) {// 顶点遍历序列List<Integer> res = new ArrayList<>();// 哈希表,用于记录已被访问过的顶点Set<Integer> visited = new HashSet<>();// 调用辅助函数进行深度优先遍历dfs(graph, visited, res, startIdx);return res;
}
主要功能
-
辅助函数
dfs
:- 该函数负责递归访问图中的顶点。
- 记录当前顶点并将其标记为已访问。
- 遍历所有邻接的顶点,如果相邻顶点未被访问,则递归调用
dfs
。
-
主函数
graphDFS
:- 接收一个
GraphAdjMat
对象和起始顶点的索引。 - 初始化结果列表
res
和已访问顶点集合visited
。 - 调用
dfs
辅助函数开始深度优先遍历并返回最终的访问顺序。
- 接收一个
注意事项
- 确保在构造图时已知起始顶点的索引。
- 深度优先遍历适合用于搜索路径、分析连通性等场景。