Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,14 @@ function genAlgorithmDataStructures() {
"2022-10-01-tree-2-3.md",
"2022-10-02-tree-red-black.md",
]
},
{
title: "图论",
collapsable: false,
sidebarDepth: 0,
children: [
"2022-10-03-graph.md",
]
}

]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public class Node {
相比于AVL树通过左右旋转平衡树高,红黑树则是在2-3树的基础上,只对黑色节点维护树高,所以它会使用到染色和左右旋来对树高调衡。*染色与左右旋相比,减少了平衡操作*

- 源码地址:[https://github.com/fuzhengwei/java-algorithms](https://github.com/fuzhengwei/java-algorithms)
- 本章源码:[https://github.com/fuzhengwei/java-algorithms/tree/main/data-structures/src/main/java/stack](https://github.com/fuzhengwei/java-algorithms/tree/main/data-structures/src/main/java/tree)
- 本章源码:[https://github.com/fuzhengwei/java-algorithms/tree/main/data-structures/src/main/java/tree](https://github.com/fuzhengwei/java-algorithms/tree/main/data-structures/src/main/java/tree)
- 动画演示:[https://www.cs.usfca.edu/~galles/visualization/RedBlack.html](https://www.cs.usfca.edu/~galles/visualization/RedBlack.html)—— 红黑树初次理解还是比较困难的,可以结合学习内容的同时做一些动画演示。

### 1. 左倾染色
Expand Down
315 changes: 315 additions & 0 deletions docs/md/algorithm/data-structures/2022-10-03-graph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
---
title: 图 Graph
lock: need
---

# 图 Graph

作者:小傅哥
<br/>博客:[https://bugstack.cn](https://bugstack.cn)

> 沉淀、分享、成长,让自己和他人都能有所收获!😄

## 一、前言

`图的历史`

Leonhard Euler 于1736 年发表的[关于柯尼斯堡七桥](https://en.wikipedia.org/wiki/Seven_Bridges_of_K%C3%B6nigsberg)的论文被认为是图论史上的第一篇论文。这篇论文,以及范德蒙德写的关于骑士问题的论文,都是在莱布尼茨发起的分析位置上进行的。欧拉公式涉及凸多面体的边数、顶点数和面数,由 Cauchy 和L'Huilier 研究和推广,它代表了称为拓扑的数学分支的开始。

1860 年和 1930 年拓扑学的自主发展通过 Jordan、Kuratowski 和 Whitney 的著作使图论得以发展。图论和[拓扑学](https://en.wikipedia.org/wiki/Topology)共同发展的另一个重要因素来自现代代数技术的使用。这种用途的第一个例子来自物理学家古斯塔夫·基尔霍夫的工作,他在 1845 年发表了他的基尔霍夫电路定律,用于计算电路中的电压和电流。

## 二、图的数据结构

图(Graph)结构是一种比树结构复杂的非线性的数据结构,图在实际生活中的例子非常多,比如;地铁线路网、微信好友关系链、计算机中的状态执行等,都可以抽象成图的结构。

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E) = 【G表示图、V表示顶点个数、E表示边的个数】。图的数据结构是多对多关系,就像你的微信好友可能也是我的微信好友,且相互交叉对应。与之对应的是树,树是1对多关系,所以树也是一种特殊的没有闭环的图。

按照图**是否有方向**和**是否有权重**可以分为一下4类组合;

| U/U | U/W | D/U | D/W |
| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
| ![](https://bugstack.cn/images/article/algorithm/graph-01.png) | ![](https://bugstack.cn/images/article/algorithm/graph-02.png) | ![](https://bugstack.cn/images/article/algorithm/graph-03.png) | ![](https://bugstack.cn/images/article/algorithm/graph-04.png) |
| 无向图&无权重 | 无向图&有权重 | 有向图&无权重 | 有向图&有权重 |

- 顶点:图中的任意节点都算作顶点,图中任意两个顶点间都可能存在连接,如果没有顶点间没有连线则称为空图。
- 无向图:图中任意两个顶点间都没有指向,则称这样的图为无向图。
- 有向图:图中任意两个顶点间都有指向边,则称这样的图为有向图。
- 无权重:图中任意两个顶点间的连线,没有权重值,则无权重。
- 有权重:图中任意两个顶点间的连线,包含权重值,则有权重。

## 三、图的结构实现

图的结构实现可以基于数组、链表和红黑树实现,也因此将使用数组实现的图称为邻接矩阵,链表和红黑树实现的图称为邻接表。

```java
// 图的顶点数
protected int v;
// 图的边个数
protected int e;

// 图的矩阵【数组】
protected int[][] table;
// 图的矩阵【链表】
protected LinkedList<Integer[]>[] table;
// 图的矩阵【红黑树】
private TreeSet<Integer>[] table;
```

图的数据存放可以使用 int 数组、LinkedList 链表、TreeSet 红黑树等方式存储。

- 源码地址:[https://github.com/fuzhengwei/java-algorithms](https://github.com/fuzhengwei/java-algorithms)
- 本章源码:[https://github.com/fuzhengwei/java-algorithms/tree/main/data-structures/src/main/java/graph](https://github.com/fuzhengwei/java-algorithms/tree/main/data-structures/src/main/java/graph)
- 动画演示:[https://visualgo.net/zh/graphds](https://visualgo.net/zh/graphds)—— 图结构初次理解还是比较困难的,可以结合学习内容的同时做一些动画演示。

### 1. 邻接矩阵【数组】

#### 1.1 无向图&无权重

<div align="center">
<img src="https://bugstack.cn/images/article/algorithm/graph-05.png?raw=true" width="750px">
</div>

```java
// 图的顶点数
protected int v;
// 图的边个数
protected int e;
// 图的矩阵
protected int[][] table;

// 对称插入,无方向,无权重
public void insert(int x, int y) {
table[x][y] = 1;
table[y][x] = 1;
}
```

- 邻接矩阵通过数组存放元素,会有一些浪费空间,所有的空间都会填满。
- 在插入元素的时候,对称插入节点。例如:0→1、1→0,两个方向都插入元素。

#### 1.2 有向图&有权重

<div align="center">
<img src="https://bugstack.cn/images/article/algorithm/graph-06.png?raw=true" width="750px">
</div>

```java
// 图的顶点数
protected int v;
// 图的边个数
protected int e;
// 图的矩阵
protected int[][] table;

// 对称插入,无方向,无权重
public void insert(int x, int y, int weight) {
table[x][y] = weight;
}
```

- 邻接矩阵通过数组存放元素,会有一些浪费空间,所有的空间都会填满。
- 在插入元素的时候,插入单向节点,节点值为权重值。例如:0→2,权重值是4。

### 2. 邻接表【链表】

#### 1.1 无向图&无权重

<div align="center">
<img src="https://bugstack.cn/images/article/algorithm/graph-07.png?raw=true" width="750px">
</div>

```java
// 图的顶点数
protected int v;
// 图的边个数
protected int e;
// 图的矩阵
protected LinkedList<Integer[]>[] table;

// 对称插入,无方向,无权重
public void insert(int x, int y) {
table[x].add(new Integer[]{y});
table[y].add(new Integer[]{x});
}
```

- 通过数组+链表的实现方式可以减少非必要的元素存储,更加节省空间。
- 其实插入元素的过程和数组类似,无向无权重直接对称插入元素即可。

#### 1.2 有向图&有权重

<div align="center">
<img src="https://bugstack.cn/images/article/algorithm/graph-08.png?raw=true" width="750px">
</div>

```java
// 图的顶点数
protected int v;
// 图的边个数
protected int e;
// 图的矩阵
protected LinkedList<Integer[]>[] table;

// 对称插入,有方向,有权重
public void insert(int x, int y, int weight) {
table[x].add(new Integer[]{y, weight});
}
```

- 通过数组+链表的实现方式可以减少非必要的元素存储,更加节省空间。
- 其实插入元素的过程和数组类似,有方向有权重则只插入单个指向,并需要通过数组或者对象的方式记录权重值。

### 3. 遍历

图的最终实现是通过 TreeSet 红黑树的方式,这样即节省空间,又能提高元素的索引和遍历效率。

```java
// 图的顶点数
private int v;
// 图的边个数
private int e;
// 图的矩阵
private TreeSet<Integer>[] table;
```

#### 3.1 无向图&有向图

| 无向图 | 有向图 |
| :----------------------------------------------------------: | :----------------------------------------------------------: |
| ![](https://bugstack.cn/images/article/algorithm/graph-09.png) | ![](https://bugstack.cn/images/article/algorithm/graph-10.png) |
| 对称插入:table[x].add(y); table[y].add(x); | 单向插入:table[x].add(y); |

#### 3.2 广度遍历&深度遍历


| U/U | U/U | D/U | D/U |
| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
| ![](https://bugstack.cn/images/article/algorithm/graph-11.png) | ![](https://bugstack.cn/images/article/algorithm/graph-12.png) | ![](https://bugstack.cn/images/article/algorithm/graph-13.png) | ![](https://bugstack.cn/images/article/algorithm/graph-14.png) |
| 深度遍历 | 广度遍历 | 深度遍历 | 广度遍历 |

通过四个图的的对比,可以看到;
1. 深度遍历,不断地向下探测。广度遍历横行探测。
2. 当有权重时候,则深度和广度会按照权重进行选择优先遍历的顺序。

```java
public void bfs(int s) {
Queue<Integer> queue = new LinkedList<>();
visited[s] = true;
queue.add(s);
while (!queue.isEmpty()) {
int v = queue.remove();
order.add(v);
for (int w : G.adj(v)) {
if (!visited[w]) {
queue.add(w);
visited[w] = true;
}
}
}
}

private void dfs(int v) {
visited[v] = true;
// 深度优先,前序遍历
pre.add(v);
for (int w : graph.adj(v)) {
if (!visited[w]) {
dfs(w);
}
}
// 深度优先,后序遍历
post.add(v);
}
```

## 四、图实现测试

### 1. 邻接矩阵

```java
@Test
public void test_U_U() {
AdjacencyMatrixArray.U_U matrix = new AdjacencyMatrixArray.U_U(6, 9);
matrix.insert(0, 1);
matrix.insert(0, 3);
matrix.insert(0, 2);
matrix.insert(0, 4);
matrix.insert(1, 4);
matrix.insert(2, 4);
matrix.insert(2, 5);
matrix.insert(3, 5);
System.out.println(matrix);
}
```

- 在单元测试中共提供了UU、UW、DU、DW四种情况,读者可自行验证。

```java
图配置:V = 6, E = 9
---------------------------
| 0 | 1 | 2 | 3 | 4 | 5 |
⏛⏛⏛⏛⏛⏛⏛⏛⏛⏛⏛⏛⏛⏛⏛⏛
0 | 0 | 1 | 1 | 1 | 1 | 0 |
---------------------------
1 | 1 | 0 | 0 | 0 | 1 | 0 |
---------------------------
2 | 1 | 0 | 0 | 0 | 1 | 1 |
---------------------------
3 | 1 | 0 | 0 | 0 | 0 | 1 |
---------------------------
4 | 1 | 1 | 1 | 0 | 0 | 0 |
---------------------------
5 | 0 | 0 | 1 | 1 | 0 | 0 |
---------------------------
```

- 通过测试结果可以看到一个邻接矩阵无向图且无权重的存放结果。

### 2. 邻接链表

```java
@Test
public void test_D_W() {
AdjacencyMatrixList.D_W matrix = new AdjacencyMatrixList.D_W(6, 9);
matrix.insert(0, 1, 1);
matrix.insert(0, 3, 2);
matrix.insert(0, 2, 3);
matrix.insert(0, 4, 5);
matrix.insert(1, 4, 1);
matrix.insert(2, 4, 3);
matrix.insert(2, 5, 7);
matrix.insert(3, 5, 4);
System.out.println(matrix);
}
```

- 在单元测试中共提供了UU、UW、DU、DW四种情况,读者可自行验证。

**测试结果**

```java
图配置:V = 6, E = 9
---------------------------
0 | → [1,1] → [3,2] → [2,3] → [4,5]
---------------------------
1 | → [4,1]
---------------------------
2 | → [4,3] → [5,7]
---------------------------
3 | → [5,4]
---------------------------
4 |
---------------------------
5 |
---------------------------
```

- 通过测试结果可以看到邻接链表在对图元素的存储上,非常节省空间,并记录了权重值。

## 五、常见面试题

- 图的使用场景是什么?
- 图有的分类?
- 图怎么存放权重值?
- 图的广度遍历
- 图的深度遍历