三、【图算法】深度优先搜索(DFS)

图算法是个庞大的家族,其中大部分成员的主体框架都可以归结于图的遍历。图的遍历需要访问所有顶点一次且仅 一次,此外,图遍历同时还要访问所有的边一次且仅一次。经过一次遍历,树边的顶点共同构成了原图的一颗遍历树

为防止对顶点的重复访问,在遍历的过程中,需要动态地设置各顶点的不同状态,并且随着遍历的进程不断地转换状态,直至最后的“访问完毕”,图的遍历更加强调对处于特定状态顶顶啊的甄别和查找,故也称作图搜索

最基本而典型的图搜索算法包括:广度优先搜索(BFS),深度优先搜索(DFS),优先级搜索等(PFS),本文主要介绍图的深度优先搜索(depth-first search,DFS),本文使用的图数据结构参见之前博客https://blog.csdn.net/qq_18108083/article/details/84870399

策略:优先选取最后一个被访问到的顶点的邻居。始自图中顶点s的BFS搜索,将首先访问顶点s,再从s所有尚未访问到的邻居中任取一个顶点,并以此为基点,递归地执行DFS搜索。这个过程具有先入先后的特点,故采用栈结构作为辅助容器或直接使用递归方法,这里使用递归进行实现。

实现:由于整个图可能具有多个连通域,从单个顶点开始的DFS可能不能遍历到图中的所有顶点,所以DFS函数能够遍历从顶点s开始的单个连通域,而dfs函数则对所有顶点进行检查,只要未曾被访问过,就从该点开始一次新的DFS搜索,这样就能保证所有的连通域都能够被遍历到。

template<typename Tv, typename Te> void graph<Tv, Te>::DFS(int v, int& clock)
{
	status(v) = DISCOVERED;    //标记当前节点为发现
	dTime(v) = ++clock;

	for (int u = firstNbr(v); u > -1; u = nextNbr(v, u))  //遍历所有邻居顶点
	{
		switch (status(u))
		{
		case UNDISCOVERED:   //尚未发现的顶点,继续深入遍历
			status(u) = DISCOVERED;  //标记为已发现
			type(v, u) = TREE;
			parent(u) = v;
			DFS(u, clock);
			break;
		case DISCOVERED:     //已被发现但是尚未遍历完成的顶点,那就是祖先啊
			type(v, u) = BACKWARD;
			break;
		default:   //VISITED  已经遍历完成,根据dTime判断是FORWARD还是CROSS
			type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;
			break;
		}
	}
	status(v) = VISITED;
	fTime(v) = ++clock;
}

template<typename Tv, typename Te> void graph<Tv, Te>::dfs(int s)
{
	reset();   //复位所有顶点和已存在边的状态为未被发现,未确定
	int clock = 0;  //时间标签
	int v = s;
	do
	{
		if (status(v) == UNDISCOVERED)
			DFS(v, clock);   //对每个顶点都进行一次单连通域深度优先搜索
		cout << "v----" << v << endl;
	} while ((v=(++v%n)) != s);
}

效率:若图G=(V,E)中共有n个顶点和e条边,则DFS仅需O(n+e)时间。

2. 系统设计 1.用到的抽象数据类型的定义 的抽象数据类型定义: ADT Graph{ 数据对象V:V是具有相同特性的数据元素的集合,称为顶点集 数据关系R: R={VR} VR={<v,w>|v,w∈V且P(v,w),<v,w>表示从v到w的弧, 谓词P(v,w)定义了弧<v,w>的意义或信息} 基本操作P: CreatGraph(&G,V,VR) 初始条件:V是的顶点集,VR是中弧的集合 操作结果:按V和VR的定义构造G DestroyGraph(&G) 初始条件:G存在 操作结果:销毁G InsertVex(&G,v) 初始条件:G存在,v和中顶点有相同特征 操作结果:在G中增添新顶点v …… InsertArc(&G,v,w) 初始条件:G存在,v和w是G中两个顶点 操作结果:在G中增添弧<v,w>,若G是无向的则还增添对称弧<w,v> …… DFSTraverse(G,Visit()) 初始条件:G存在,Visit是顶点的应用函数 操作结果:对进行深度优先遍历,在遍历过程中对每个顶点调用函数Visit一次且仅一次。一旦Visit()失败,则操作失败 BFSTraverse(G,Visit()) 初始条件:G存在,Visit是顶点的应用函数 操作结果:对进行广度优先遍历,在遍历过程中对每个顶点调用函数Visit一次且仅一次。一旦Visit()失败,则操作失败 }ADT Graph 栈的抽象数据类型定义: ADT Stack{ 数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0} 数据关系:R1={<ai-1,ai>|ai-1,ai∈D,i=2,…,n} 约定an端为栈顶,ai端为栈底 基本操作: InitStack(&S) 操作结果:构造一个空栈S DestroyStack(&S) 初始条件:栈S已存在 操作结果:将S清为空栈 StackEmpty(S) 初始条件:栈S已存在 操作结果:若栈S为空栈,则返回TRUE,否则FALSE …… Push(&S,e) 初始条件:栈S已存在 操作结果:插入元素e为新的栈顶元素 Pop(&S,&e) 初始条件:栈S已存在且非空 操作结果:删除S的栈顶元素,并用e返回其值 StackTraverse(S,visit()) 初始条件:栈S已存在且非空 操作结果:从栈底到栈顶依次对S的每个数据元素调用函数visit(),一旦visit()失败,则操作失效 }ADT Stack 队列的抽象数据类型定义: ADT Queue{ 数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0} 数据关系:Rl={<ai-1,ai>|ai-1,ai∈D,i=2,…,n} 约定其中ai端为队列头,an端为队列尾。 基本操作: InitQueue(&Q) 操作结果:构造一个空队列Q DestroyQueue(&Q) 初始条件:队列Q已存在 操作结果:队列Q被销毁,不再存在 QueueEmpty(Q) 初始条件:队列Q已存在 操作结果:若Q为空队列,则返回TRUE,否则FALSE …… EnQueue(&Q,e) 初始条件:队列Q已存在 操作结果:插入元素e为Q的新的队尾元素 DeQueue(&Q,&e) 初始条件:Q为非空队列 操作结果:删除Q的队头元素,并用e返回其值 }ADT Queue 2.主程序的流程: 调用CreateDN函数创建的邻接表G; 调用PrintDN函数输出邻接表G; 调用DFSTraverse函数深度优先遍历; 调用BFSTraverse函数广度优先遍历
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值