java dfs bfs 有向图_BFS和DFS分别用于有向图和无向图的一点心得和总结

本文详细介绍了有向图的BFS和DFS算法实现,通过邻接链表数据结构,讨论了在有向图中如何进行广度优先搜索和深度优先搜索。在BFS中,使用队列进行辅助,而DFS则使用栈,同时考虑了非强连通图的情况。文章还对比了无向图的BFS和DFS,强调了在无向图中设置三种状态的重要性,以避免重复访问节点的问题。最后,作者建议在学习算法时,结合纸笔进行思考以加深理解。
摘要由CSDN通过智能技术生成

图的数据结构为邻接链表adjacency list。

listVertex是一个储存Vertex* 顶点类指针的vector类型的STL;在Vertex类中有一个类成员nextEdgeNode,他是储存pair类型的vector容器;数对pair的first表示边指向的顶点序号,second表示边的序号。

四个函数都只有一个参数,在声明时提供了默认的参数值-1;参数表示起始顶点的编号

先放一个测试用的图、有向图的邻接链表、无向图的邻接链表。

ab4421749fc06e4dd9fcea5ddf5965b2.png

175a466d48e7d17b4edbb9c4eac25048.png

769a309089b5bd4a84261e8b7331393d.png

以下是有向图的BFS

1 void Graph::BFSListO(intstartFrom) {2 /*the principle is to firstly choose a random vertex, if it has 0 out degree, then choose the first3 unvisited vertex in the list; moreover,if one way is exhausted to continue,also push the first univisited vertex4 in the list to the queue*/

5 cout << "BFS of oriented Adjacey List:" <

9 while (listVertex[rdm]->nextEdgeNode.empty()) {10 rdm = rand() % sze;//randomly choose a vertex whose out degree is not 0

11 }12 queuemyQueue;13 if(startFrom==-1)//default treatment is random choose

14 myQueue.push(listVertex[rdm]);15 else {//designated choose

16 myQueue.push(listVertex[startFrom-1]);17 }18 while (!myQueue.empty()) {//travel through until there is nothing in the queue

19 Vertex* V =myQueue.front();20 if (V->color == 0) {21 V->color = 1; //O black(unvisited) & 1 white(visited)//V visited white 1

22 cout << "Vertex visited:" << V->id +1<nextEdgeNode.empty()) {//if the out degree is not zero

25 for (auto it = V->nextEdgeNode.begin(); it != V->nextEdgeNode.end(); it++) {//travel through all outgoing vertices

26 if(listVertex[(*it).first - 1]->color==0)27 myQueue.push(listVertex[(*it).first-1]);//push into queue for next round travel

28 }29 }30 else {//if the first chosed vertex has zero out degree, then add the first unvisited vertex from list

31 for (auto it = listVertex.begin(); it != listVertex.end(); it++) {32 if ((*it)->color == 0) {//if unvisited

33 myQueue.push(*it);34 break;/*ATTENTION without this line the algo will be wrong !35 because more than one unvisited vertex has been added to queue,36 which causes a sequential fault*/

37 }38 }39 }40 myQueue.pop();41

42 }43 }

其实有向图比无向图复杂得多。19行以前都在初始化,把第一个顶点放入队列。这里使用FIFO队列进行辅助。每一次循环,总是先访问队头顶点;然后从这个顶点开始遍历(不是访问)他的所有子顶点,将他们加入队尾;最后把这个元素弹出队列。

这里需要注意有向图的特殊之处:有的顶点可能出度为零,因此需要加入26和32行的判断语句,如果被访问了的这个顶点没有子顶点,则按顺序选择一个未被访问过的顶点,加入队尾(注意只能选择一个哦,不要忘记break)

输出示例:

1a4e57d586ccc06be084f2f3ace7c6f0.png

以下是无向图的BFS

1 void Graph::BFSListN(intstartFrom) {2 /*the principle is to firstly choose a random vertex, if it has 0 out degree, then choose the first3 unvisited vertex in the list; moreover,if one way is exhausted to continue,also push the first univisited vertex4 in the list to the queue*/

5 cout << "BFS of non-oriented Adjacey List:" <

9 while (listVertex[rdm]->nextEdgeNode.empty()) {10 rdm = rand() % sze;//randomly choose a vertex whose out degree is not 0

11 }12 queuemyQueue;13 if (startFrom == -1) {//default treatment is random choose

14 myQueue.push(listVertex[rdm]);15 listVertex[rdm]->color = 2;16 }17 else {//designated choose

18 myQueue.push(listVertex[startFrom - 1]);19 listVertex[startFrom - 1]->color = 2;20 }21 while (!myQueue.empty()) {//travel through until there is nothing in the queue

22 Vertex* V =myQueue.front();23 V->color = 1; //O black(unvisited) & 1 white(visited) & 2 gray(now in queue)//V visited white 1

24 cout << "Vertex visited:" << V->id + 1 <nextEdgeNode.begin(); it != V->nextEdgeNode.end(); it++) {//travel through all outgoing vertices

26 if (listVertex[(*it).first - 1]->color !=1&& listVertex[(*it).first - 1]->color !=2){27 myQueue.push(listVertex[(*it).first - 1]);//push into queue for next round travel

28 listVertex[(*it).first - 1]->color = 2;29 }30 }31 myQueue.pop();32 }33 }

无向图要简单多了,关键看两个不同之处,一是这里设置了三个颜色(23行),二是while中的内容简洁得多。

至于为什么这里要设置三种状态(未访问0、已访问1、已入队列2),主要是这里的算法如果用两种状态会有问题。假设两种状态(0未访问、1已访问),如果从7开始访问(置1),好,3、4、5依次加入队尾(无状态设置);然后7被弹出,访问3(置1),并把3的所有非1(未访问)的子顶点4、6加入队尾,这里就发现4被重复加入了队列一次,这是不正确的。

while中的逻辑就很简单,每次访问完某顶点之后,把其所有未被访问过、且不在队列中的顶点加入队尾就行了。

输出示例:

ddd1e66614dc899e50a4110d16e97bfc.png

以下是有向图的非递归DFS

1 void Graph::DFSListO(intstartFrom) {2 cout << "Non recursive DFS of oriented Adjacey List:" <

6 while (listVertex[rdm]->nextEdgeNode.empty()) {7 rdm = rand() % sze;//randomly choose a vertex whose out degree is not 0

8 }9 stackmyStack;10 Vertex*tempV;11 int countVisitedAll = 0;//to control overall number of visited vertices

12 if (startFrom == -1) {13 tempV = listVertex[rdm];//randomly chosed

14 }15 else

16 {17 tempV = listVertex[startFrom - 1];//designated

18 }19 tempV->color = 1;//O black(unvisited) & 1 white(visited)

20 cout << "Vertex visited:" << tempV->id + 1 <

26 for (auto it = tempV->nextEdgeNode.begin(); it != tempV->nextEdgeNode.end(); it++) {27 if (listVertex[(*it).first-1]->color==0) {//if an unvisited vertex has been found

28 listVertex[(*it).first - 1]->color = 1;//O black(unvisited) & 1 white(visited)

29 cout << "Vertex visited:" << listVertex[(*it).first - 1]->id + 1 <

31 countVisitedAll++;32 break;33 }34 else//if a visited vertex found

35 {36 countVisited++;//then increment the count

37 }38 }39 if (countVisited == tempV->nextEdgeNode.size()) {40 myStack.pop();41 }42 if (countVisitedAll != listVertex.size() &&myStack.empty()) {43 for (auto it = listVertex.begin(); it != listVertex.end(); it++)44 if ((*it)->color == 0) {45 (*it)->color = 1;//O black(unvisited) & 1 white(visited)

46 cout << "Vertex visited:" << (*it)->id+ 1 <

使用栈作为辅助数据结构,所有的顶点在访问后被压入栈中。当当前顶点的所有邻接顶点都已被访问时,弹出当前顶点(39-40行);当其还有邻接顶点未被访问时,则从第一个未被访问的顶点开始访问,并将其入栈(26-32行,注意break)。另外对于有向图DFS要考虑到一种情况,就是非强连通图(测试用图就是非强连通图)的问题:如果从初始选择的顶点开始进行DFS,在遍历完他所在的连通分量之后,栈中所有元素都将被弹出,这时如果不作处理,遍历就会结束,这是错误的(如图中从4开始的话,访问完4、7、5就结束)。42-49行做的,就是在访问完其中一个连通分量后,按顺序选择一个未访问过的顶点,访问之,并入栈。这样保证了算法的通用性。

输出示例:

f46aba1014f5467276bae5ea5339a783.png

最后一个是递归的无向图DFS

1 void Graph::DFSListN(intstartFrom) {2 cout << "Recursive DFS of non-oriented Adjacey List:" <

6 while (listVertex[rdm]->nextEdgeNode.empty()) {7 rdm = rand() % sze;//randomly choose a vertex whose out degree is not 0

8 }9 Vertex*tempV;10 if (startFrom == -1)11 tempV =listVertex[rdm];12 else

13 tempV = listVertex[startFrom - 1];14 if (tempV->color == 0) {//not visited

15 tempV->color = 1;//O black(unvisited) & 1 white(visited)

16 cout << "Vertex visited:" << tempV->id+1 <nextEdgeNode.begin(); it != tempV->nextEdgeNode.end(); it++) {18 if (listVertex[(*it).first - 1]->color == 0) {19 DFSListN((*it).first);20 }21 }22 }23 }

无向图的递归真的很简单哦,每次递归时,tempV都指向前一次访问的顶点的第一个未访问邻接顶点;当tempV已经没有未访问的邻接顶点时,退出本次递归(相当于非递归中的顶点出栈)。

输出示例:(由于递归,输出有点难看)

f5c08ca7fbf03717f347a2b527ed773c.png

总结:编程、尤其是算法,是需要经常训练的····一切纸上谈兵都是没用的。另外对于这些图的算法,脑子里想不清楚建议拿出纸笔,把一步步的步骤写出来、画出来,会提高效率。写下这篇博客,加深记忆。

文中如有谬误,敬请读者指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值