当前位置:
文档之家› 图的遍历(深度优先遍历和广度优先遍历 )
图的遍历(深度优先遍历和广度优先遍历 )
while (栈S不空) { v = 栈S顶部元素; 求v的下个未访问过的出点i; 访问i; 为i置已访问标志; i进栈S; nNodes++; if (v已无未被访问过的出点) 出栈; }
return nNodes;
} 上面的伪码描述与具体的数据结构无关。下面的程序分别给 出了针对邻接矩阵与邻接表的算法模型。
long nNodes, i, v; long top; char *visited; long *s; visited = new char[n]; for (i=0; i<n; i++) visited[i]=0; s = new long[n+1]; top=0; nNodes=0; resu[nNodes++]=v0; //将访问到的结点依次存于resu[] visited[v0]=1; //为v0置已访问标志
if (g[v0][i]!=0) //若<v0, i>是边 if (!visited[i]) //若结点i未被访问过 nNodes = nNodes+DFS1(g, n, i, visited,resu); //从i起深度优先遍历图
} return nNodes;
}
上面函数中的参数visited和top实质上是中间变量,只是为
1.一般方法 下面考虑深度优先遍历的非递归实现的一般方法(存储
结构无关)。
图的深度优先遍历的非递归实现,仍然与二叉树的前序 遍历非递归实现相似。不同之处有:①二叉树每个结点至 多有两个可达邻接点(左右儿子),而图的可达邻接点数 目不定,因此,当结点重新出现在栈顶时,不能一定出栈, 而是要根据它的各出点是否都已被访问过来决定(是则出 栈);②对二叉树,沿可达邻接点搜索时不会发生回绕, 但对图,若不加特别控制,就有可能回绕。
{//深度优先遍历图(递归)。图g为邻接矩阵,结点编号为 0~n. 返回实际遍历到的结点数目
//visited是访问标志数组,调用本函数前,应为其分配空间 并初始化为全0(未访问)
//resu为一维数组,用于存放所遍历到的结点的编号,调 用本函数前,应为其分配空间
long nNodes, i; nNodes=1; resu[top++]=v0; //将访问到的结点依次存于resu[] visited[v0]=1; //为v0置已访问标志 for (i=0; i<n; i++) {//依次从v0的各个出点出发,深度优先遍历图
显然,因为我们没有规定对出点的遍历次序, 所以,图的深度优先遍历结果一般不唯一。
例如,对图 20-1给出的有向图与无向图,一些遍历 结果(结点访问次序)为:
左图:从1出发:1,2,4,5;或1,5,2,4 从2出发:2,1,5,4;或2,4,1,5
右图:从a出发:a,b,c,d;或a,b,d, c; … …
访问标志的设置有两种方法:
①在描述图结的记录中增设一个访问标志位。 这种方法的优点是,访问“访问标志”的方法与 访问结点的方法一致。如果访问标志需要与图结 构同生命期,则这种方法比较合适。但是,若访 问标志要重复使用,就必须先重新初始化访问标 志。如果图结点的存储不利于顺序访问,这往往 也是个遍历问题!
20、 图的遍历
从这节起,我们介绍图的一些重要操作的实现, 包括遍历、拓扑排序、关键路径等。另有一些重要 操作,如最短路径问题、最小生成树问题,由于主 要难点在于算法,所以我们安排在后面的算法设计 章节中介绍。
图的遍历与树的遍历一样具有十分重要的作用, 图的许多其他操作(算法)都与遍历相关。
20.1 概述
了避免在递归调用时重新初始化而放在参数表中,造成使
用的不方便,为此,做个A包装如程果序不: 想让visited或 top做为函数参数,也
long DFS1(int g[][CNST可_N以um在No函des数], l中ong将n,其lon定g v0,
long *resu )
义为static型量。但是,
1 a
2
5
b
d
4
3
c
图20- 1一个有向图(左)和无向图
(二)递归实现方法
1. 一般算法 下面考虑深度优先遍历的递归实现的一般方法(存储
结构无关)。
图的深度优先遍历与二叉树的前序遍历相似。不同之 处有:①二叉树每个结点至多有两个可达邻接点 (左右儿子),而图的可达邻接点数目不定;
②对二叉树,沿可达邻接点搜索时不会发生回绕, 但对图,若不加特别控制,就有可能回绕。
//v0为遍历的起点。返回实际遍历到的结点的数目。 //visited是访问标志数组,调用本函数前,应为其分配空间并初 始化为全0(未访问) //resu为一维数组,用于存放所遍历到的结点的编号,调用本函 数前,应为其分配空间
long nNodes, i;
TGraphEdgeAL<TEdgeElem> *p;
4
由于1点已访问过,跳过, 找到4,记标识,送输出,
5
Nodes[]
对应的邻接表
4有作为新的始点重复上 述过程
输出数组
resu
3.邻接表深度优先遍历的实现
template <class TElem, class TEdgeElem>long
DFS2(TGraphNodeAL<TElem, TEdgeElem> *nodes,long n,long v0, char *visited, long *resu,long &top) {//深度优先遍历用邻接表表示的图。nodes是邻接表的头数组,n 为结点个数(编号为0~n)。
历
nNodes = nNodes+DFS2(nodes, n, p->endNo, visited, resu,top);
p = p->next; //令p指向v0的下个出点
}
return nNodes;
} 与邻接矩阵的情况类似,也可以对该程序“包装”,以隐蔽visited 和top。
(三) 非递归实现方法
nNodes=1; 求出v0的第1个可达邻接点v; while (v存在)
{ if (v未被访问过) nNodes=nNodes+DFS(g,v); 求出v0的下个可达邻接点v;
}
return nNodes;
}
1
2
5
4
3
12345 1 01001 2 10010 3 00001 4 10000 5 00000
下面是图的深度优先遍历递归算法的一般性描述。 如果要另设一个数组作为访问标志,则该数组要在 递归过程(函数)之外初始化为“未访问”。
long DFS(图g,结点v0) { //图深度优先遍历递归算法。从结点v0出发,深度优
先遍历图g,返回访问到的结点总数 int nNodes; //寄存访问到的结点数目 访问v0; 为v0置已访问标志;
下面只考虑从一点出发遍历,因此有可能 会出现遍历不到的点。即那些初始点无路 径可达的点,是遍历不到的。
20.2 深度优先遍历
(一) 遍历规则
从图中某结点v0出发,深度优先遍历(DFS: Depth First Search)图的规则为:
·访问v0; ·对v0的各个出点v01,v02,…,v0m,每次从它 们中按一定方式(也可任选)选取一个未被访问过 的结点,从该结点出发按深度优先遍历方式遍历。
图的遍历的含意是,从图中某结点出发,按某既定方式 访问图中各个可访问到的结点,使每个可访问到的结点恰被 访问一次。
图的遍历方式有两种:深度优先与广度优先方式,分别 对应于树的先根遍历与层序遍历。
树中不存在回路,但图中可能有回路。因此,当沿回路 进行扫描时,一个结点可能被扫描到多次,可能导致死循环。 为了避免这种情形,在遍历中,应为每个结点设立一个访问 标志,每扫描到一个结点,要检查它的访问标志,若标志为 “未访问”,则按正常方式对其进行处理(如访问或转到它 的邻接点等),否则放过它,扫描下一个结点。
{
这样的程序是不可再
char *visited; long top=0; visited = new
入 char[n]调;
的,即函数再次 用 时 , static 型 的
被 量
for (long i=0; i<n; i++也) vi不site重d[i新]=0初; 始 化 , 造
long num=DFS1( g, n成, v错0, v误isi!ted, resu, top );
深度优先遍历非递归算法的一般性描述。
long DFS_NR(图g,结点v0) { //图深度优先遍历非递归算法。从结点v0出发,深度优 先遍历图g,返回访问到的结点总数
int nNodes; //寄存访问到的结点数目 访问v0; 为v0置已访问标志; v0进栈S; nNodes=1; 求出v0的第1个可达邻接点v;
top++; s[top]=v0; while (top!=0) { v=s[top]; for (i=0; i<n; i++) //寻找v的下个未访问的邻接点
if (g[v][i]!=0) //若<v, i>是边 if (!visited[i]) //若结点i未被访问过 { resu[nNodes++]=i; //将访问到的结点依次存于resu[] visited[i]=1; //为i置已访问标志 top++; s[top]=i; //i进栈 break; }
delete visited;
return num;
}
1
邻接表边
11Βιβλιοθήκη P 地址 起 终 权 信息 链
21
2
5