【数据结构与算法基础】代码仓库:https://github.com/jinrunheng/datastructure-and-algorithm
一:数据结构遍历的意义
图是一种数据结构,每种数据结构,都必须有“遍历”的方式,很多算法,本质都是遍历。
对于线性数据结构
遍历是从头到尾访问线性结构的每个元素。
对于一棵二叉树,也存在着多种遍历方式,按照访问树的节点的顺序可以分为:前序,中序,后序遍历以及层序遍历。前序,中序,后序遍历都是一种深度优先遍历,按层遍历是一种广度优先遍历。
图的遍历整体也可以分为两大类:深度优先遍历与广度优先遍历。
那么图的遍历和树的遍历有什么本质的不同呢?
假设我的图结构如上图所示,如果我使用树的深度优先遍历方式来遍历我们的图,我们就会发现,图是具有环状结构的,会产生重复遍历的情况。在对图遍历的时候,我们需要记录,哪个节点被遍历过了。
二:从树的深度优先遍历到图的深度优先遍历
树的深度优先遍历
拿前序遍历来说,树的优先遍历伪码如下:
perirder(root);
preorder(TreeNode node){
if(node != null) {
list.add(node.val);
preorder(node.left);
preorder(node.right);
}
}
图的深度优先遍历
图的深度优先遍历伪码如下,我们会声明一个数组,记录哪个节点已经被遍历过,在逻辑上与树的深度优先遍历其实并没有本质上的不同,只是我们访问了一个节点,就要在声明的数组中,将访问的节点标记为已访问的状态。
visited[0...V-1] = false;
dfs(0);
dfs(int v){
visited[v] = true;
list.add(v);
for(int w : adj(v))
if(!visited[w])
dfs(w);
}
三:DFS 逻辑的微观解读
接下来,我们来具体看一下对于下面个图,深度优先遍历的过程是怎样的?
我们从 dfs(0) 开始。
四:实现图的深度优先遍历
代码如下:
visited[0...V] = false;
for(int v = 0; v < V; v++)
if(!visited[v])
dfs(v);
dfs(int v) {
visited[v] = true;
list.add(v);
for(int w : adj(v))
if(!visited[w])
dfs(w);
}
这是深度优先遍历的前序遍历方式,同理,也有深度优先遍历的后续遍历方式:
visited[0...V] = false;
for(int v = 0; v < V; v++)
if(!visited[v])
dfs(v);
dfs(int v) {
visited[v] = true;
for(int w : adj(v))
if(!visited[w])
dfs(w);
list.add(v);
}
五:更多关于图的深度优先遍历
图的深度优先遍历的应用
- 求图的联通分量
- 求两点间是否可达
- 求两点间的一条路径
- 检测图中是否有环
- 二分图检测
- 寻找图中的桥
- 寻找图中的割点
- 哈密尔顿路径
- 拓扑排序
- … …
非递归方式实现图的深度优先遍历
非递归方式实现图的深度优先遍历代码如下:
visited[0...V] = false;
for(int v = 0; v < V; v++)
if(!visited[v])
dfs(v);
dfs(int v) {
Stack<Integer> stack = new Stack<>();
stack.push(v);
visited[v] = true;
while (!stack.isEmpty()) {
int cur = stack.pop();
list.add(cur);
for (int w : G.adj(cur)) {
if (!visited[w]) {
stack.push(w);
visited[w] = true;
}
}
}
}