人的知识就好比一个圆圈,圆圈里面是已知的,圆圈外面是未知的。你知道得越多,圆圈也就越大,你不知道的也就越多。

0%

数据结构--图

概念

  • 图(有向图、无向图、带权图)
  • 顶点
  • 度(入度、出度)

存储

邻接矩阵

邻接矩阵的底层依赖一个二维数组。
对于无向图来说,如果顶点i与顶点j之间有边,我们就将A[i][j]和A[j][i]标记为1。
对于有向图来说,如果顶点i到顶点j之间,有一条箭头从顶点i指向顶点j的边,那我们就将A[i][j] 标记为 1。同理,如果有一条箭头从顶点 j 指向顶点 i 的边,我们就将 A[j][i] 标记为 1。
对于带权图,数组中就存储相应的权重。
邻接矩阵存储示例

优缺点

优点

邻接矩阵的存储方式简单、直接,因为基于数组,所以在获取两个顶点的关系时,就非常高效。其次,用邻接矩阵存储图的另外一个好处是方便计算。

缺点

对于无向图来说,如果我们将其用对角线划分为上下两部分,那我们只需要利用上面或者下面这样一半的空间就足够了,另外一半白白浪费掉了。
如果我们存储的是稀疏图(Sparse Matrix),也就是说,顶点很多,但每个顶点的边并不多,那邻接矩阵的存储方法就更加浪费空间了。

邻接表存储

每个顶点对应一条链表,链表中存储的是与这个顶点相连接的其他顶点。
如下图:是一个有向图的邻接表存储方式,每个顶点对应的链表里面,存储的是指向的顶点。
邻接表存储示例(有向图)
对于无向图来说,也是类似的,不过,每个顶点的链表中存储的,是跟这个顶点有边相连的顶点。

优缺点

邻接表存储起来比较节省空间,但是使用起来就比较耗时间。

搜索

广度优先

直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索。
广度优先搜索示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public void bfs(int s, int t) {
if (s == t) {
return;
}

// 记录已经被访问的顶点
boolean[] visited = new boolean[v];
visited[s] = true;

// 存储已经访问,但相连的顶点还没有被访问的顶点
Queue<Integer> queue = new LinkedList<>();
queue.add(s);

// 记录搜索路径(prev[w] 存储的是,顶点 w 是从哪个前驱顶点遍历过来)
int[] prev = new int[v];
for (int i = 0; i < v; ++i) {
prev[i] = -1;
}

while (queue.size() != 0) {
int w = queue.poll();
for (int i = 0; i < adj[w].size(); ++i) {
int q = adj[w].get(i);
if (!visited[q]) {
prev[q] = w;
if (q == t) {
print(prev, s, t);
return;
}
visited[q] = true;
queue.add(q);
}
}
}
}

/**
* 递归打印 s->t 的路径
*/
private void print(int[] prev, int s, int t) {
if (prev[t] != -1 && t != s) {
print(prev, s, prev[t]);
}
System.out.print(t + " ");
}

深度优先

最直观的例子就是“走迷宫”。
深度优先搜索示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 是否已找到
*/
private boolean found = false;

/**
* 深度优先遍历
*/
public void dfs(int s, int t) {
found = false;

// 记录已经被访问的顶点
boolean[] visited = new boolean[v];

// 记录搜索路径(prev[w] 存储的是,顶点 w 是从哪个前驱顶点遍历过来)
int[] prev = new int[v];
for (int i = 0; i < v; ++i) {
prev[i] = -1;
}

recurDfs(s, t, visited, prev);

print(prev, s, t);
}

/**
* 递归遍历
*/
private void recurDfs(int w, int t, boolean[] visited, int[] prev) {
if (found) {
return;
}

visited[w] = true;

if (w == t) {
found = true;
return;
}

for (int i = 0; i < adj[w].size(); ++i) {
int q = adj[w].get(i);
if (!visited[q]) {
prev[q] = w;
recurDfs(q, t, visited, prev);
}
}
}
小礼物走一走,来 Github 关注我