DFS,depth-first search,深度优先搜索。顾名思义,从一个节点出发,尽可能往下遍历,即尽可能离“家”远一点,这个思想其实就是树结构遍历中的先序遍历。
那么从上述话语中,我们可以很容易地判断出需要用到递归,其实DFS的常规构造方法也就是用到递归的,但是我们还要考虑到一个问题,那么就是如果一个图足够大,比如有上万个节点,上万条边,那么估计用递归运行DFS我们的电脑会瞬间卡死。。。因为递归的代价实在是太大了,图如果小一点还好,不是特别明显,一旦图过大,递归的劣势会被无限放大,因此这里除了用递归来构造DFS以外,我还会介绍非递归的方法,即通过使用数据结构——栈来代替函数调用,虽说栈的出现会导致占用过多的内存空间,但是,时代变了!区区一个栈再大能够有多大呢?再宝贵能有时间宝贵吗?所以在大型的图深度遍历中,使用栈的DFS绝对要比使用递归的DFS的效率要高!
递归算法转化为非递归算法的优点:
- 节省内存空间
- 提高执行效率
一些特殊情况
首先我们需要考虑到一些特殊情况:
- 某些图存在环路,因此我们需要避免因为环路导致的死循环,即重复遍历相同的节点。
- 解决方案就是,添加一个标志位mark,未遍历的节点mark为0
- 某些图并不是所有的节点都连接在一起,即从一个节点出发可能无法遍历所有的节点。
- 解决方案就是,在一次遍历结束时,检查标志数组mark[],通过查看其所有的mark是否为0,来判断是否遍历完所有的节点。
解决完这些特殊情况之后,那么接下来就进入到了正式环节:
常规版本——递归DFS
先上伪代码
因为伪代码没有其他语言的语法限制,能够有助于我们更好地将注意力放在算法上,所以我们先用伪代码来讲述一下主要思想:
algorithm DFS(G)
//实现给定图G的广度优先搜索
//输入:图G=<V, E>,其中V为G中所有的节点集合,E为G中所有的边集合
//输出:图G的节点,按照DFS遍历访问到的先后顺序
count <- 0
for 每个属于V的v do
mark[v] <- 0 //将每个节点的标志位都赋初值为0,表示未被访问
for 每个属于V的v do
if mark[v] = 0
dfsVisit(v)
dfsVisit(v)
count <- count + 1
visit(v)
mark[v] <- count
for 每个与v相邻的节点w do
if mark[w] = 0
dfsVisit(w)
由以上代码便实现了DFS,接下来只需要使用某一种特定的高级语言来实现即可。
在实现之前,我们先来探讨一下其中的一些细节:
1.以上代码是否实现了遍历所有节点的要求呢?
首先在DFS()中,我们在调用dfsVisit()函数之前,是用了一个循环遍历所有的节点,然后若一个节点未被遍历,则会调用dfsVisit()函数,随后将与其相连接的节点全部遍历完,因此能够实现遍历所有的节点。
for 每个属于V的