文章目录
- 1.1 走迷宫
- 1.2 图的深度优先搜索实现
- 1.3 算法分析及性能
- 1. 4 单点连通性
- 后记
1.1 走迷宫
简单的迷宫,如下图1.1-1所示:
探索迷宫而不迷路,我们需要:
- 选择一条没有标记过的通道,在你走过的路上铺一条绳子;
- 标记所有你第一次路过的路口和通道;
- 当来到一个标记过的路口时(用绳子)回退到上一个路口;
- 当回退的路口已没有可走的通道时继续回退。
绳子可以保证总能找到一条出路,标记能保证你不会两次经过同一条通道或者路口。我们把上迷宫,用等价的图来代替,如下图1.1-2所示:
1.2 图的深度优先搜索实现
图的遍历算法非递归代码如下:
import java.util.Map;
/**
* key-value pair 类
* @author: Administrator
* @createTime: 2023/03/04 21:49
*/
public final class Entry<K, V> implements Map.Entry<K, V>{
private K key;
private V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
V oldValue = value;
this.value = value;
return oldValue;
}
}
/**===========*/
import com.gaogzhen.datastructure.stack.Stack;
import edu.princeton.cs.algs4.Graph;
import java.util.Iterator;
/**
* 单点连通性
* @author: Administrator
* @createTime: 2023/03/03 19:58
*/
public class DepthFirstSearch {
/**
* 顶点是否标记
*/
private boolean[] marked;
/**
* 与指定顶点连通的顶点总数
*/
private int count;
/**
* 图
*/
private Graph graph;
/**
* 起点
*/
private int s;
public DepthFirstSearch(Graph graph, int s) {
this.graph = graph;
this.s = s;
check(s);
marked = new boolean[graph.V()];
dfs();
}
/**
* 搜索图g中与起点v连通的所有顶点
*/
private void dfs() {
Stack<Entry<Integer, Iterator<Integer>>> path = new Stack<>();
// marked[v] = true;
if (!marked[s]) {
marked[s] = true;
count++;
Iterable<Integer> iterable = graph.adj(s);
Iterator<Integer> it;
if (iterable != null && (it = iterable.iterator()) != null){
path.push(new Entry<>(s, it));
}
}
while (!path.isEmpty()) {
Entry<Integer, Iterator<Integer>> entry = path.pop();
int x;
Iterator<Integer> it = entry.getValue();
Integer f = entry.getKey();
while (it.hasNext()) {
x = it.next();
if (!marked[x]) {
marked[x] = true;
count++;
if (it.hasNext()) {
path.push(entry);
}
Iterable<Integer> iterable = graph.adj(x);
if (iterable != null && (it = iterable.iterator()) != null){
path.push(new Entry<>(x, it));
}
break;
}
}
}
}
/**
* 检测索引是否在范围之内
* @param i 给定索引
*/
private void check(int i) {
if (i < 0 || i > graph.V() - 1) {
throw new IndexOutOfBoundsException("索引越界异常");
}
}
/**
* 判断起点是否与给定顶点x连通
* @param x 给定顶点
* @return
*/
public boolean marked(int x) {
check(x);
return marked[x];
}
/**
* 返回图中与顶点想连通的顶点数
* @return
*/
public int count() {
return count;
}
}
测试代码:
public static void testDepth() {
String path = "H:\\gaogzhen\\java\\projects\\algorithm\\asserts\\maze.txt";
In in = new In(path);
Graph graph = new Graph(in);
int s = 0;
DepthFirstSearch depthFirstSearch = new DepthFirstSearch(graph, s);
int t = 5;
System.out.println(depthFirstSearch.marked(t));
System.out.println(depthFirstSearch.count());
}
// 测试结果
true
6
1.3 算法分析及性能
知识点:
- Entry类就是一个键值对类,存在一对键值;在DepthFirstSearch中key用于存储顶点序号,value存储该顶点对应邻接顶点迭代器。
- 深度优先搜索非递归实现,主要借助栈来代替递归调用栈帧结构,已节省内存占用和提高运行效率。
算法分析:dfs()方法为该算法实现的主要方法,方法源代码以给出,这里不再赘述整体流程,着重分析下以下一个关键问题。
- 该非递归dfs方法如何保证深度优先?
- 首先我把键值对Entry起点和起点对应的邻接(连通)顶点集合迭代器压入栈中
- 外层循环开始
- 弹出栈顶元素,获取Entry的顶点及对应的邻接顶点集合迭代器
- 内层循环判断该迭代器有下一个元素即还有邻接顶点,取出一个邻接顶点。
- 判断改邻接顶点没有被标记过
- 标记数组对应顶点索引标记
- 连通顶点计数+1
- 判断迭代器还有元素,重新压入栈中
- break跳出内层循环
- 判断改邻接顶点没有被标记过
- 总结:只要邻接顶点(更深一层的顶点)没被标记过,标记之后同层迭代器压入栈中,去访问更深一层的顶点;而不是继续访问同层的顶点。
- 该dfs方法如果保证同层(同一个顶点的邻接顶点)访问全部访问完毕且只访问一次?
- 每个顶点只访问一次是标记数组marked[]索引和顶点一一对应,默认都是false未标记;标记之后不会在压入栈中,自然不会在标记一次
- 访问同层元素是通过迭代器完成的,while配合迭代hasNext,next()方法保证全部访问一边且不会重复访问。
- 深度优先算法性能如何?见下面的命题及证明。
- 这里根据上面的算法简单分析
- 完成循环判断栈不为空,那么只有未被标记的顶点及其邻接表(迭代器)会放入栈中;也就是说所有的顶点及其邻接表都会被放入栈中且不会重复
- 内层判断迭代器有元素那么所有的邻接表会被遍历一边,邻接表代表对应顶点的度数
- 结论深度优先搜索标记与起点连通的所有顶点所需的时间和顶点的度数之和成正比
命题A。深度优先搜索标记与起点连通的所有顶点所需的时间和顶点的度数之和成正比。
证明:首先,我们要证明这个算法能标记所有与起点s连通的所有顶点(且不会标记其他顶点)。算法仅通过边来寻找顶点,所以每个被标记的顶点都与s连通;反证法证明标记了所有与s连通的顶点,假设某个没有被标记的顶点w与s连通。因为s作为起点是被标记的,由s到w的任意一条路径中至少有一条边连接的两个顶点分别被标记过河没有被标记过,例如v-x。根据算法,在标记了v后比如发现x,因此这样的边不存在。
每个顶点都只会被标记一次保证了时间上限(检查标记的耗时和度数成正比)。
详细搜索轨迹,可参考算法第四版341页。
1. 4 单点连通性
连通性。给定一幅图,回答“两个给定的顶点是否连通”?或者图中有多少个连通子图等类似问题。
问题“两个给定的顶点是否连通?”等价于“两个给定的顶点间是否存在一条路径?”,也可以叫做路经检测问题。深度优先搜索解决了这个问题。
递归方法参考《算法第四版?或者书提供的jar包。
后记
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm
参考链接:
[1][美]Robert Sedgewich,[美]Kevin Wayne著;谢路云译.算法:第4版[M].北京:人民邮电出版社,2012.10.P338-P342.