From 7060433b9da5a912ba2d48f919e4cd2ee379086a Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sun, 29 Jan 2023 01:10:53 +0800 Subject: [PATCH] Update the structure of the chapter Graph. --- .../adjacency_list_add_edge.png | Bin .../adjacency_list_add_vertex.png | Bin .../adjacency_list_initialization.png | Bin .../adjacency_list_remove_edge.png | Bin .../adjacency_list_remove_vertex.png | Bin .../adjacency_matrix_add_edge.png | Bin .../adjacency_matrix_add_vertex.png | Bin .../adjacency_matrix_initialization.png | Bin .../adjacency_matrix_remove_edge.png | Bin .../adjacency_matrix_remove_vertex.png | Bin .../chapter_graph/basic_operation_of_graph.md | 329 ++++++++++++++++++ docs/chapter_graph/graph.md | 326 ----------------- mkdocs.yml | 3 +- 13 files changed, 331 insertions(+), 327 deletions(-) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_list_add_edge.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_list_add_vertex.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_list_initialization.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_list_remove_edge.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_list_remove_vertex.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_matrix_add_edge.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_matrix_add_vertex.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_matrix_initialization.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_matrix_remove_edge.png (100%) rename docs/chapter_graph/{graph.assets => basic_operation_of_graph.assets}/adjacency_matrix_remove_vertex.png (100%) create mode 100644 docs/chapter_graph/basic_operation_of_graph.md diff --git a/docs/chapter_graph/graph.assets/adjacency_list_add_edge.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_add_edge.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_list_add_edge.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_add_edge.png diff --git a/docs/chapter_graph/graph.assets/adjacency_list_add_vertex.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_add_vertex.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_list_add_vertex.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_add_vertex.png diff --git a/docs/chapter_graph/graph.assets/adjacency_list_initialization.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_initialization.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_list_initialization.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_initialization.png diff --git a/docs/chapter_graph/graph.assets/adjacency_list_remove_edge.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_remove_edge.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_list_remove_edge.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_remove_edge.png diff --git a/docs/chapter_graph/graph.assets/adjacency_list_remove_vertex.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_remove_vertex.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_list_remove_vertex.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_list_remove_vertex.png diff --git a/docs/chapter_graph/graph.assets/adjacency_matrix_add_edge.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_add_edge.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_matrix_add_edge.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_add_edge.png diff --git a/docs/chapter_graph/graph.assets/adjacency_matrix_add_vertex.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_add_vertex.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_matrix_add_vertex.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_add_vertex.png diff --git a/docs/chapter_graph/graph.assets/adjacency_matrix_initialization.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_initialization.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_matrix_initialization.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_initialization.png diff --git a/docs/chapter_graph/graph.assets/adjacency_matrix_remove_edge.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_remove_edge.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_matrix_remove_edge.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_remove_edge.png diff --git a/docs/chapter_graph/graph.assets/adjacency_matrix_remove_vertex.png b/docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_remove_vertex.png similarity index 100% rename from docs/chapter_graph/graph.assets/adjacency_matrix_remove_vertex.png rename to docs/chapter_graph/basic_operation_of_graph.assets/adjacency_matrix_remove_vertex.png diff --git a/docs/chapter_graph/basic_operation_of_graph.md b/docs/chapter_graph/basic_operation_of_graph.md new file mode 100644 index 000000000..3710c8b5a --- /dev/null +++ b/docs/chapter_graph/basic_operation_of_graph.md @@ -0,0 +1,329 @@ +--- +comments: true +--- + +# 图基础操作 + +图的基础操作分为对「边」的操作和对「顶点」的操作,在「邻接矩阵」和「邻接表」这两种表示下的实现方式不同。 + +## 基于邻接矩阵的实现 + +设图的顶点总数为 $n$ ,则有: + +- **添加或删除边**:直接在邻接矩阵中修改指定边的对应元素即可,使用 $O(1)$ 时间。而由于是无向图,因此需要同时更新两个方向的边。 +- **添加顶点**:在邻接矩阵的尾部添加一行一列,并全部填 $0$ 即可,使用 $O(n)$ 时间。 +- **删除顶点**:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 $(n-1)^2$ 个元素“向左上移动”,从而使用 $O(n^2)$ 时间。 +- **初始化**:传入 $n$ 个顶点,初始化长度为 $n$ 的顶点列表 `vertices` ,使用 $O(n)$ 时间;初始化 $n \times n$ 大小的邻接矩阵 `adjMat` ,使用 $O(n^2)$ 时间。 + +=== "初始化邻接矩阵" + ![adjacency_matrix_initialization](basic_operation_of_graph.assets/adjacency_matrix_initialization.png) + +=== "添加边" + ![adjacency_matrix_add_edge](basic_operation_of_graph.assets/adjacency_matrix_add_edge.png) + +=== "删除边" + ![adjacency_matrix_remove_edge](basic_operation_of_graph.assets/adjacency_matrix_remove_edge.png) + +=== "添加顶点" + ![adjacency_matrix_add_vertex](basic_operation_of_graph.assets/adjacency_matrix_add_vertex.png) + +=== "删除顶点" + ![adjacency_matrix_remove_vertex](basic_operation_of_graph.assets/adjacency_matrix_remove_vertex.png) + +以下是基于邻接矩阵表示图的实现代码。 + +=== "Java" + + ```java title="graph_adjacency_matrix.java" + /* 基于邻接矩阵实现的无向图类 */ + class GraphAdjMat { + List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + /* 构造函数 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new ArrayList<>(); + this.adjMat = new ArrayList<>(); + // 添加顶点 + for (int val : vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (int[] e : edges) { + addEdge(e[0], e[1]); + } + } + + /* 获取顶点数量 */ + public int size() { + return vertices.size(); + } + + /* 添加顶点 */ + public void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.add(val); + // 在邻接矩阵中添加一行 + List newRow = new ArrayList<>(n); + for (int j = 0; j < n; j++) { + newRow.add(0); + } + adjMat.add(newRow); + // 在邻接矩阵中添加一列 + for (List row : adjMat) { + row.add(0); + } + } + + /* 删除顶点 */ + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfBoundsException(); + // 在顶点列表中移除索引 index 的顶点 + vertices.remove(index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.remove(index); + // 在邻接矩阵中删除索引 index 的列 + for (List row : adjMat) { + row.remove(index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + public void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) + adjMat.get(i).set(j, 1); + adjMat.get(j).set(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.get(i).set(j, 0); + adjMat.get(j).set(i, 0); + } + } + ``` + +=== "C++" + + ```cpp title="graph_adjacency_matrix.cpp" + + ``` + +=== "Python" + + ```python title="graph_adjacency_matrix.py" + + ``` + +=== "Go" + + ```go title="graph_adjacency_matrix.go" + + ``` + +=== "JavaScript" + + ```js title="graph_adjacency_matrix.js" + + ``` + +=== "TypeScript" + + ```typescript title="graph_adjacency_matrix.ts" + + ``` + +=== "C" + + ```c title="graph_adjacency_matrix.c" + + ``` + +=== "C#" + + ```csharp title="graph_adjacency_matrix.cs" + + ``` + +=== "Swift" + + ```swift title="graph_adjacency_matrix.swift" + + ``` + +## 基于邻接表的实现 + +设图的顶点总数为 $n$ 、边总数为 $m$ ,则有: + +- **添加边**:在顶点对应链表的尾部添加边即可,使用 $O(1)$ 时间。因为是无向图,所以需要同时添加两个方向的边。 +- **删除边**:在顶点对应链表中查询与删除指定边,使用 $O(m)$ 时间。与添加边一样,需要同时删除两个方向的边。 +- **添加顶点**:在邻接表中添加一个链表即可,并以新增顶点为链表头结点,使用 $O(1)$ 时间。 +- **删除顶点**:需要遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(n + m)$ 时间。 +- **初始化**:需要在邻接表中建立 $n$ 个结点和 $2m$ 条边,使用 $O(n + m)$ 时间。 + +=== "初始化邻接表" + ![adjacency_list_initialization](basic_operation_of_graph.assets/adjacency_list_initialization.png) + +=== "添加边" + ![adjacency_list_add_edge](basic_operation_of_graph.assets/adjacency_list_add_edge.png) + +=== "删除边" + ![adjacency_list_remove_edge](basic_operation_of_graph.assets/adjacency_list_remove_edge.png) + +=== "添加顶点" + ![adjacency_list_add_vertex](basic_operation_of_graph.assets/adjacency_list_add_vertex.png) + +=== "删除顶点" + ![adjacency_list_remove_vertex](basic_operation_of_graph.assets/adjacency_list_remove_vertex.png) + +基于邻接表实现图的代码如下所示。 + +=== "Java" + + ```java title="graph_adjacency_list.java" + /* 顶点类 */ + class Vertex { + int val; + public Vertex(int val) { + this.val = val; + } + } + + /* 基于邻接表实现的无向图类 */ + class GraphAdjList { + // 请注意,vertices 和 adjList 中存储的都是 Vertex 对象 + Map> 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 == vet2) + throw new IllegalArgumentException(); + // 添加边 vet1 - vet2 + adjList.get(vet1).add(vet2); + adjList.get(vet2).add(vet1); + } + + /* 删除边 */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 删除边 vet1 - vet2 + adjList.get(vet1).remove(vet2); + adjList.get(vet2).remove(vet1); + } + + /* 添加顶点 */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // 在邻接表中添加一个新链表(即 HashSet) + adjList.put(vet, new HashSet<>()); + } + + /* 删除顶点 */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // 在邻接表中删除顶点 vet 对应的链表(即 HashSet) + adjList.remove(vet); + // 遍历其它顶点的链表(即 HashSet),删除所有包含 vet 的边 + for (Set set : adjList.values()) { + set.remove(vet); + } + } + } + ``` + +=== "C++" + + ```cpp title="graph_adjacency_list.cpp" + + ``` + +=== "Python" + + ```python title="graph_adjacency_list.py" + + ``` + +=== "Go" + + ```go title="graph_adjacency_list.go" + + ``` + +=== "JavaScript" + + ```js title="graph_adjacency_list.js" + + ``` + +=== "TypeScript" + + ```typescript title="graph_adjacency_list.ts" + + ``` + +=== "C" + + ```c title="graph_adjacency_list.c" + + ``` + +=== "C#" + + ```csharp title="graph_adjacency_list.cs" + + ``` + +=== "Swift" + + ```swift title="graph_adjacency_list.swift" + + ``` + +## 效率对比 + +设图中共有 $n$ 个顶点和 $m$ 条边,下表为邻接矩阵和邻接表的时间和空间效率对比。 + +
+ +| | 邻接矩阵 | 邻接表(链表) | 邻接表(哈希表) | +| ------------ | -------- | -------------- | ---------------- | +| 判断是否邻接 | $O(1)$ | $O(m)$ | $O(1)$ | +| 添加边 | $O(1)$ | $O(1)$ | $O(1)$ | +| 删除边 | $O(1)$ | $O(m)$ | $O(1)$ | +| 添加顶点 | $O(n)$ | $O(1)$ | $O(1)$ | +| 删除顶点 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | +| 内存空间占用 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | + +
+ +观察上表,貌似邻接表(哈希表)的时间与空间效率最优。但实际上,在邻接矩阵中操作边的效率更高,只需要一次数组访问或赋值操作即可。总结以上,**邻接矩阵体现“以空间换时间”,邻接表体现“以时间换空间”**。 diff --git a/docs/chapter_graph/graph.md b/docs/chapter_graph/graph.md index 395b2a60b..ca81b20ae 100644 --- a/docs/chapter_graph/graph.md +++ b/docs/chapter_graph/graph.md @@ -72,332 +72,6 @@ $$ 观察上图发现,**邻接表结构与哈希表「链地址法」非常相似,因此我们也可以用类似方法来优化效率**。比如,当链表较长时,可以把链表转化为「AVL 树」,从而将时间效率从 $O(n)$ 优化至 $O(\log n)$ ,还可以通过中序遍历获取有序序列;还可以将链表转化为 HashSet(即哈希表),将时间复杂度降低至 $O(1)$ ,。 -## 图基础操作 - -以下分别介绍图在「邻接矩阵」和「邻接表」表示下的基础操作。 - -### 基于邻接矩阵的实现 - -设图的顶点总数为 $n$ ,则有: - -- **添加或删除边**:直接在邻接矩阵中修改指定边的对应元素即可,使用 $O(1)$ 时间。而由于是无向图,因此需要同时更新两个方向的边。 -- **添加顶点**:在邻接矩阵的尾部添加一行一列,并全部填 $0$ 即可,使用 $O(n)$ 时间。 -- **删除顶点**:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 $(n-1)^2$ 个元素“向左上移动”,从而使用 $O(n^2)$ 时间。 -- **初始化**:传入 $n$ 个顶点,初始化长度为 $n$ 的顶点列表 `vertices` ,使用 $O(n)$ 时间;初始化 $n \times n$ 大小的邻接矩阵 `adjMat` ,使用 $O(n^2)$ 时间。 - -=== "初始化邻接矩阵" - ![adjacency_matrix_initialization](graph.assets/adjacency_matrix_initialization.png) - -=== "添加边" - ![adjacency_matrix_add_edge](graph.assets/adjacency_matrix_add_edge.png) - -=== "删除边" - ![adjacency_matrix_remove_edge](graph.assets/adjacency_matrix_remove_edge.png) - -=== "添加顶点" - ![adjacency_matrix_add_vertex](graph.assets/adjacency_matrix_add_vertex.png) - -=== "删除顶点" - ![adjacency_matrix_remove_vertex](graph.assets/adjacency_matrix_remove_vertex.png) - -以下是基于邻接矩阵表示图的实现代码。 - -=== "Java" - - ```java title="graph_adjacency_matrix.java" - /* 基于邻接矩阵实现的无向图类 */ - class GraphAdjMat { - List vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” - List> adjMat; // 邻接矩阵,行列索引对应“顶点索引” - - /* 构造函数 */ - public GraphAdjMat(int[] vertices, int[][] edges) { - this.vertices = new ArrayList<>(); - this.adjMat = new ArrayList<>(); - // 添加顶点 - for (int val : vertices) { - addVertex(val); - } - // 添加边 - // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 - for (int[] e : edges) { - addEdge(e[0], e[1]); - } - } - - /* 获取顶点数量 */ - public int size() { - return vertices.size(); - } - - /* 添加顶点 */ - public void addVertex(int val) { - int n = size(); - // 向顶点列表中添加新顶点的值 - vertices.add(val); - // 在邻接矩阵中添加一行 - List newRow = new ArrayList<>(n); - for (int j = 0; j < n; j++) { - newRow.add(0); - } - adjMat.add(newRow); - // 在邻接矩阵中添加一列 - for (List row : adjMat) { - row.add(0); - } - } - - /* 删除顶点 */ - public void removeVertex(int index) { - if (index >= size()) - throw new IndexOutOfBoundsException(); - // 在顶点列表中移除索引 index 的顶点 - vertices.remove(index); - // 在邻接矩阵中删除索引 index 的行 - adjMat.remove(index); - // 在邻接矩阵中删除索引 index 的列 - for (List row : adjMat) { - row.remove(index); - } - } - - /* 添加边 */ - // 参数 i, j 对应 vertices 元素索引 - public void addEdge(int i, int j) { - // 索引越界与相等处理 - if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) - throw new IndexOutOfBoundsException(); - // 在无向图中,邻接矩阵沿主对角线对称,即满足 (i, j) == (j, i) - adjMat.get(i).set(j, 1); - adjMat.get(j).set(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.get(i).set(j, 0); - adjMat.get(j).set(i, 0); - } - } - ``` - -=== "C++" - - ```cpp title="graph_adjacency_matrix.cpp" - - ``` - -=== "Python" - - ```python title="graph_adjacency_matrix.py" - - ``` - -=== "Go" - - ```go title="graph_adjacency_matrix.go" - - ``` - -=== "JavaScript" - - ```js title="graph_adjacency_matrix.js" - - ``` - -=== "TypeScript" - - ```typescript title="graph_adjacency_matrix.ts" - - ``` - -=== "C" - - ```c title="graph_adjacency_matrix.c" - - ``` - -=== "C#" - - ```csharp title="graph_adjacency_matrix.cs" - - ``` - -=== "Swift" - - ```swift title="graph_adjacency_matrix.swift" - - ``` - -### 基于邻接表的实现 - -设图的顶点总数为 $n$ 、边总数为 $m$ ,则有: - -- **添加边**:在顶点对应链表的尾部添加边即可,使用 $O(1)$ 时间。因为是无向图,所以需要同时添加两个方向的边。 -- **删除边**:在顶点对应链表中查询与删除指定边,使用 $O(m)$ 时间。与添加边一样,需要同时删除两个方向的边。 -- **添加顶点**:在邻接表中添加一个链表即可,并以新增顶点为链表头结点,使用 $O(1)$ 时间。 -- **删除顶点**:需要遍历整个邻接表,删除包含指定顶点的所有边,使用 $O(n + m)$ 时间。 -- **初始化**:需要在邻接表中建立 $n$ 个结点和 $2m$ 条边,使用 $O(n + m)$ 时间。 - -=== "初始化邻接表" - ![adjacency_list_initialization](graph.assets/adjacency_list_initialization.png) - -=== "添加边" - ![adjacency_list_add_edge](graph.assets/adjacency_list_add_edge.png) - -=== "删除边" - ![adjacency_list_remove_edge](graph.assets/adjacency_list_remove_edge.png) - -=== "添加顶点" - ![adjacency_list_add_vertex](graph.assets/adjacency_list_add_vertex.png) - -=== "删除顶点" - ![adjacency_list_remove_vertex](graph.assets/adjacency_list_remove_vertex.png) - -基于邻接表实现图的代码如下所示。 - -=== "Java" - - ```java title="graph_adjacency_list.java" - /* 顶点类 */ - class Vertex { - int val; - public Vertex(int val) { - this.val = val; - } - } - - /* 基于邻接表实现的无向图类 */ - class GraphAdjList { - // 请注意,vertices 和 adjList 中存储的都是 Vertex 对象 - Map> 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 == vet2) - throw new IllegalArgumentException(); - // 添加边 vet1 - vet2 - adjList.get(vet1).add(vet2); - adjList.get(vet2).add(vet1); - } - - /* 删除边 */ - public void removeEdge(Vertex vet1, Vertex vet2) { - if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) - throw new IllegalArgumentException(); - // 删除边 vet1 - vet2 - adjList.get(vet1).remove(vet2); - adjList.get(vet2).remove(vet1); - } - - /* 添加顶点 */ - public void addVertex(Vertex vet) { - if (adjList.containsKey(vet)) - return; - // 在邻接表中添加一个新链表(即 HashSet) - adjList.put(vet, new HashSet<>()); - } - - /* 删除顶点 */ - public void removeVertex(Vertex vet) { - if (!adjList.containsKey(vet)) - throw new IllegalArgumentException(); - // 在邻接表中删除顶点 vet 对应的链表(即 HashSet) - adjList.remove(vet); - // 遍历其它顶点的链表(即 HashSet),删除所有包含 vet 的边 - for (Set set : adjList.values()) { - set.remove(vet); - } - } - } - ``` - -=== "C++" - - ```cpp title="graph_adjacency_list.cpp" - - ``` - -=== "Python" - - ```python title="graph_adjacency_list.py" - - ``` - -=== "Go" - - ```go title="graph_adjacency_list.go" - - ``` - -=== "JavaScript" - - ```js title="graph_adjacency_list.js" - - ``` - -=== "TypeScript" - - ```typescript title="graph_adjacency_list.ts" - - ``` - -=== "C" - - ```c title="graph_adjacency_list.c" - - ``` - -=== "C#" - - ```csharp title="graph_adjacency_list.cs" - - ``` - -=== "Swift" - - ```swift title="graph_adjacency_list.swift" - - ``` - -### 效率对比 - -设图中共有 $n$ 个顶点和 $m$ 条边,下表为邻接矩阵和邻接表的时间和空间效率对比。 - -
- -| | 邻接矩阵 | 邻接表(链表) | 邻接表(哈希表) | -| ------------ | -------- | -------------- | ---------------- | -| 判断是否邻接 | $O(1)$ | $O(m)$ | $O(1)$ | -| 添加边 | $O(1)$ | $O(1)$ | $O(1)$ | -| 删除边 | $O(1)$ | $O(m)$ | $O(1)$ | -| 添加顶点 | $O(n)$ | $O(1)$ | $O(1)$ | -| 删除顶点 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | -| 内存空间占用 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | - -
- -观察上表,貌似邻接表(哈希表)的时间与空间效率最优。但实际上,在邻接矩阵中操作边的效率更高,只需要一次数组访问或赋值操作即可。总结以上,**邻接矩阵体现“以空间换时间”,邻接表体现“以时间换空间”**。 - ## 图常见应用 现实中的许多系统都可以使用图来建模,对应的待求解问题也可以被约化为图计算问题。 diff --git a/mkdocs.yml b/mkdocs.yml index 6181cfa2c..a091bdd7f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -164,7 +164,8 @@ nav: - 堆: - 堆(Heap): chapter_heap/heap.md - 图: - - 图(Graph): chapter_graph/graph.md + - 图(Graph): chapter_graph/graph.md + - 图基础操作: chapter_graph/basic_operation_of_graph.md - 查找算法: - 线性查找: chapter_searching/linear_search.md - 二分查找: chapter_searching/binary_search.md