图搜索 (BFS广度优先搜索 DFS宽度优先搜索)by 邓俊辉老师

广度优先搜索BFS

Breadth First Search
以s为中心,每一单位时间向外传递。
以草和火种作为比喻,源头为火种,待点燃的点为草。每单位时间向外蔓延一个单位。前锋面越来越大
在图搜索的过程中在模拟这个火种蔓延过程。

注二叉树的层次遍历也是有顺序的。 遍历(Traversal)
图搜索模板:
first in first out - Queue
先点燃的源点先进行处理,后面点燃的点enqueue。
FIFO。每一个前锋面都排成一个队列,先进先出
该算法是在队列中从队头开始 遍历搜索v的邻居。
发现后标记为discovered。 进队列。 将其变为树,引入树TREE
若发现,已经加入过 将边标记为CROSS跨边

template<typename Tv, typename Te>
void Graph<Tv, Te>::BFS(int v, int & clock){  // 任何一个节点都初始化为undiscovered
	Queue<int> Q; status(v) = DISCOVERED; Q.enqueue(v); //变为discovered clock计时
	while( !Q.empty() ){ //反复地
		int v = Q.dequeue(); dTime(v) = ++clock; //取出队首顶点v(火种),因为无法并行模拟同时的传播,所以采用clock++。
		for( int u = firstNbr(v); -1< u; u = nextNbr(v,u) ) //考察v的每一邻居u,视u的状态处理
			if(UNDISCOVERED == status(u) ){ //若u尚未被发现,则将发现该顶点,并引入树边
				status()=DISCOVERED; Q.enqueue(u); 
				type(v,u) = TREE, parent(u) = v; //v为火种
			}else  //若u已被发现(正在队列中),或者甚至已经访问完毕(已出队)
				type(v,u) = CROSS; //将(v,u)归类于跨边
		status(v) = VISITED; //变为visited,至此当前顶点访问完毕
	}
}

注意:起点可以随意选,平辈谁先后进队,随意 与结果无关。

最短路径 Shortest Path/Distance

注意BFS的最短路径是指各向同性,每条路之间,距离是相同的
距离s最短的边,一层一层的提起来。
得到BFS tree 代表火焰历史上的有效传递。
结论:任何一点,相对于起点s的最短路径,都是在tree上的。

二部图 bipartite Graph (Bigraph)

可以将图中的点分为两个部分,每个部分里面没有相连的。部分与部分之间存在相连,内部之间没有相连。可能还存在孤立的点。(可以用男生和女生的关系作比喻)

给出一个算法,判断是否是二部图?
以s开始做BFS。s 相邻的应该为异类的。
边主要分为TREE&CROSS
针对跨边CROSS—一共只可能出现两种情况。同一环路的重复点燃自己同类的 或 重复点燃了下一层的点(多点燃了,注意不会跨越两层)
二部图对于环际线之间的连接是允许的,但是对于同一环路试图连接的跨边,算法可以终止,不可以二部划分。

图Graph/树Tree —偏心距eccentricity 半径radius 中心center 直径diameter

偏心距 原本是指原始起点到中心的距离。
以原始起点做一个BFS,他会一圈一圈的向外拓展。涟漪最终会平复在某一最短路径,最短时间。last点。
为方便度量。把涟漪平复的最长路径将作为偏心距eccentricity。

center的定义 其偏心距在全局下是最小的。可以存在多个center。对于图而言,可以有无数个center,没有个数限制。
对于树而言其最多只有2两个center
radius 的定义 center对应的偏心距就是半径
diameter的定义 对园而言半径恰好是半径的两倍。图中距离最长的两个点之间的距离/通路
直径此处仅对进行分析。
binary tree的 分而治之&递归 在三个中间取max{dl,dr,hl+hr+2}
如果是一般的一棵树,只需要两次BFS就可以找到直径。
1、从任何一个点开始做一个BFS。想象成一圈一圈涟漪出去。
考察最远后代的那个点,最后停下来的点记为u。
2、从u最后停下里的点,开始做一个BFS。其也会终止于一点v。
u与v的路径,就是最长路径,也就是树的直径。时间复杂度O(n+e) 一次BFS遍历一次点n和边e。
u=BFS(s)、v=BFS(u)
反证法,如果最长路径xy与 su路径 没有交点:也就是 最长路径xy 仅与 su路径有一个共同祖先s。与s相连,xy相交的点记为t
如果最长路径xy与 su路径存在一个交点t。
这两中情况的反证逻辑为,如果最长路径没有经过u点,那么从s做bfs会经过t但是t 有没有作为s的偏心距。说明su的距离大于tx或者tu距离大于tx。从而可以说明xy并非是最长路径
在这里插入图片描述

圆桌骑士问题

召唤骑士,聚集到一个公共的位置在最短的时间内聚集。可以转换为BFS图搜索问题。
可以将骑士看看马,马只能走一个局部的日字(国际象棋)。棋盘上只有部分地块,可以选。求到同一点的最短时间
所有人都要找到相对于自己的最短路。
先计算出每个骑士到达每个格子所需要的时间(步数)。直到所有的前锋面都已经结束了。但是有一部分到不了的。
假设有两个骑士,将每个单元一一对应拼接起来。取每个格子的最大点。
然后再合并的单元数值中,选取最小的值。

DFS 深度优先搜索

Depth First Search
注意 有向图 可以选择任意一条
迷宫&绳子
先从一个点开始,任意选一条路。发现一个一个的点。一条路跑到黑。
可以走过的边作为TREE EDGE.如果走了回头路,那么该边作为cross边。碰到cross边进行回退。回退的方向也是唯一的。

实现过程
三种状态 undiscovered、discovered、visited
四种边 TREE\BACKWARD\FORWARD\CROSS

template<typename Tv, typename Te> //顶点类型、边类型
void Graph<Tv, Te>::DFS(int v, int & clock){
	dTime(v) = ++clock; status(v)=DISCOVERED; //发现当前顶点v
	for(int u=firstNbr(v); -1 < u; u = nextNbr(v,u) )  //考察v的每一邻居u
		switch(status(u)){  //视u的状态,分别处理 与bfs不同,含有递归
			case UNDISCOVERED: //u尚未发现,意味着支撑树可在此拓展
				type(v,u) = TREE; parent(u) = v; DFS(u,clock); break; //以u为基准开始递归dfs
			case DISCOVERED:  //发现u正在燃烧,标记为回边
				type(v,u)=BACKWARD; break;    //BACKWARD 返回去点着祖先(正在点着的)。每次出现一个backwrad 就会出现一个loop。backwrad判断一个是否存在loop
			default: //u已访问完毕 比BFS多一种情况 因为DFS若为有向图,则存在这种情况。forward前向传播点(即祖先点后代),从dtime小到大的。但是forward不是cycle。
				type(v,u)= dTime(v)<dTime(u) ? FORWARD:CROSS; break;
			//
	}
//与BFS不同 可以进行使用递归
	status(v)= VISITED; fTime(v) = ++clock;  //finish time 与bfs不同处 
}

Parenthesis lemma

注意DFS经常容易出现森林的情况。
将dTime和fTime跨度的时间长度为横的长度。称为活跃期。
在这里插入图片描述

任何一个祖先和后代的生命期的关系。祖先的生命期必然覆盖后代的,后代的生命期必然在祖代的生命期下面。
可以用毛线打结来理解。前辈先被发现,后背后背发现。祖辈在后背全部backtrack完以后才会离开。(白发人送黑发人)

若非有血缘关系的点,那么他们的生命期,不存在重叠的情况。
可以用于判断任意两个点是否存在血缘关系(可到达?)。
如果仅用parent判断,那么时间可能比较久。直接用dtime比较快。如果覆盖了那么就有有血缘关系。

拓扑排序 topological sorting

确定有向无环图上的动态规划转移顺序。也就是确定顺路。(大学课程顺序设置,先修课程)
法一:In-Degree
从入度为零的点入手。任选其一入栈。再假装将该点及其相连的边划去。
寻找下一个零度的点。入栈。再划去该点和它相关的边。逐步如此操作。
任何时候发现不存在零入度的点,那么就不存在拓扑排序。需要特殊处理。
法二:DFS
以终为始 与串搜索类似
最后一门课,必须最为压轴的。outgoing degree为零,即没有发出去的边。
随机选取一个起始点。
采用DFS。第一个被backtrack的点。就是最终的课程。(outdegree为零,叶子节点)通过DFS找到这个点。
backtrack的特性不会再回来,等效于该点已经被删除了。不用人为的再去删除。
每一点backtrack—>该点就会入栈。
即——利用DFS做一遍,遍历完以后,其余没遍历的点,在做DFS。backtrack后入栈。

双连通分量 bi-connectivity-component

注意双连通分量是在无向图中的概念,而强连通分量是在有向图中的概念。
bi-connectivity 双连通性 // cut vertex 切分点。
hdu3844
cut-vertex 连通两块最关键的点。如果没有这个点,该图将缺失连通性。
双连通分量 bi-connectivity-component 是指在其中不存在cut vertex。(一般是环路,其有双保险功能)

如何寻找BCC双连通分量 Criteria 从任何一点出发做一个DFS。会得到一个DFS tree。
对于所有的terminal。这些terminal均不是cut-vertx,他们对整体连通性不影响。其他的点均来自这个树中。
对于root 的outdegree为一的话,也不是cut vertex。因为他的删去,不会影响其他的连通性。
如果outdegree大于1的话,必然为cut vertex。 它若删去,会影响其他的连通性。
对于内部节点 的判断。如果outdegree为1的话,他的删去对整体连通性也没有影响。
如果root outdegree大于1时,v的某一分支,其后代,能够联系到比v还高的祖先(存在backedge联系)那么v就不算cut vertex
如果其backedge 最高只能够到v,那么v就是cut vertex

hca(v) highest connected ancestor ——backedge。
hca 自己可以更新,在backtrack的过程中给,也可以传给该节点的的父亲。
hca 如何计——如果是 [dtime,ftime] 跨度越小,能够够到的祖先越多。祖先的dtime比后代出现的更早。hca就保留越小dtime的。
因为本算法不需要判断ftime。可以把hca存储在ftime的位置上。
将空闲的ftime 和hca 等价起来

#define hca(x)( fTime(x) )    //利用此处闲置的fTime[] 充当 hca[]
	template<typename Tv, typename Te> //顶点类型、边类型
	void Graph(Tv, Te)::BCC(int v, int& clock, Stack<int>& S){   //栈S记录了当下能访问到的点
		hca(v) = dTime(v) = ++clock; status(v) = DISCOVERED; //发现v
		S.push(v); //顶点v入栈,以下枚举v的所有邻居u
		for(int u = firstNbr(v); -1<u; u = nextNbr(v,u) )
			switch( status(u) ) //视u的状态分别处理
			{
				case UNDISCOVERED:
					parent(u) = v; type<v,u> = TREE; //拓展树边
					BCC(u, clock, S); //从U开始遍历
					if( hca(u) < dTime(v) ) //返回后,若u经后向边指向v的真祖先
						hca(v) = min( hca(v), hca(u) ); //则v亦如此
					else //否则,以v为关节点(u以下即是一个BCC,且其中顶点此时正集中于栈S的顶部)
						while(u != S.pop() ); //弹出当前BCC中(除v外)的所有结点  v是一个cut vertex
					break;
				case DISCOVERED:
					type(v,u) = BACKWARD;
					if( u != parent(v) )
						hca(v) = min( hca(v), dTime(u) ); //更新hca[v],越小越高
					break;
				default: //VISITED (digraphs only)
					type(v, u) = dTime(v) < dTime(u) ? FORWARD : CROSS; 
					break;
			}
		status(v) = VISITED;  //对v的访问结束
	}  
#undef |hca|

UNDISCOVERED
如果hca 比起dtime
低的话,u经backword能到v的真祖先
高的话,那么下面就是一个BCC分量,而且BCC分量都在栈顶。
直到U出现在栈顶。所有的BCC几乎都被分离出来了(因为BCC分量包括了v这个root。pop时v不会出来。)
此时,v也可以找到。v=parent(u)

DISCOVERED
当去找的时候发现了一个祖先.backedge
获得一次机会去优化hca。

default: 标记cross但是对于本算法cross边无意义。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值