C++实现简单的深度优先搜索及路径查询(无向图)
深度优先搜索(DFS)可以通过一个递归方法遍历图的所有顶点,如果该图是连通的,那么每个邻接表的元素都会被检查到。本篇内容参考《算法》第四版
1.构造一个无向图
本篇使用《算法》第4版上的一个测试用例,如下所示
graph_initial.txt
6 <–V
8 <–E
0 5
2 4
2 3
1 2
0 1
3 4
3 5
0 2
通过上篇博客中的无向图构造方法,以graph_initial.txt为参数,定义一个无向图对象,可以得到如下图结构:
测试代码如下所示:
#include "Graph_practise.hpp"
string path = "graph_initial_1.txt";
undirect_Graph graph(path);
2.在搜索一整副连通图时,深度优先搜索如何遍历图的顶点
当深度优先搜索遍历一个顶点时,有以下两个步骤:
(1)将该顶点设置为已标记
。
(2)递归地访问该顶点的还未被标记所有邻居顶点。(比如搜索1顶点时,将1顶点标记为marked,然后去搜索它的邻居顶点2,如果2没有被标记,说明还没有搜索过这个2顶点,那么就将2标记,并继续搜索2顶点的邻居顶点,待2的邻居顶点全都搜索完了,继续搜索除2以外,1顶点的其他邻居顶点,以此类推)
3.如何实现DFS来搜索一副连通图
(1) 我们为DFS算法创建一个类,类名暂时命名为DFS
class DFS
{
public:
DFS(undirect_Graph uGraph, int s);
bool isMarked(int v);
int getCount();
private:
vector<bool> marked;
int count;
void dfs(undirected_Graph uGraph, int v);
};
<1> DFS构造函数
调用该函数时,会初始化Marked容器,使其元素均为false,代表刚开始没有任何顶点被搜索, 然后把起点的标记状态设置为true,代表该起点已经被搜索;接着通过起点s, 向它的邻居顶点开始搜索,递归地调用dfs函数,dfs函数的v参数代表当前要搜索的顶点。
DFS::DFS(undirect_Graph uGraph, int s)
{
marked = vector<bool>(uGraph.countOfVertex(), false);
cout << "----------DepthFirstSearch start------------" << endl;
dfs(uGraph, s);
}
<2>dfs
该函数用来递归地对图进行搜索, 每当到达一个顶点时,将其标记状态设置为true, 使count+1,代表这个顶点第一次被搜索到,往后再遇到这个顶点的话,就不会再调用dfs来处理这个顶点了。
下一步就是直接找到这个顶点的邻居顶点集合,这个集合已经存储在uGraph对象中的VertexSet中了,直接通过接口获取即可。对这个集合的每个元素判断是否已经被标记,如果全部被标记了,就回到顶点v对应的上一个顶点(顶点v是上一个顶点的邻居顶点),继续判断上一个顶点的其他邻居顶点的标记情况。代码如下所示:
void DFS::dfs(undirect_Graph uGraph, int v)
{
cout << "----------check " << v << " -----------" << endl;
marked[v] = true;
count++;
list<Vertex*> V_uGraphVertexSet = uGraph.getGraphVertexSet()[v]->neighbours;
//对关于获取邻接表的代码若不理解可以查看上篇关于无向图类的定义
list<Vertex*>::iterator iter = V_uGraphVertexSet.begin();
for(iter; iter != V_uGraphVertexSet.end(); iter++)
{
if(!isMarke((*iter)->value))
{
cout << cout << "---------- " << (*iter)->value << " has not been marked -----------" << endl;
dfs(uGraph, (*iter)->value);
}
}
}
下面是一些关于某顶点是否被标记以及获取标记的顶点数的代码,比较简单就不多说了
bool DFS::isMarked(int w)
{
return marked[w];
}
int DFS::getCount()
{
return count;//count为成员变量
}
在调用这个DFS类的构造函数时,就开始递归地调用dfs函数,进行深度优先搜索,dfs函数运行情况如下所示:
紫色顶点代表搜索到该顶点后,发现该顶点已经被标记。
红色顶点代表搜索到该顶点后,发现该顶点没有被标记。
以上就是深度优先搜索的基本实现,它可以搜索到一个连通图的所有顶点。
4.如何实现使用深度优先搜索寻找连通图中通往某节点的路径
前面已经简要地说明了如何实现深度优先搜索,还有一个问题就是希望找到通往某节点的路径是什么。这里直接给出通往所有节点的路径方法。
通常情况下,我们要找到一条通往某节点的路径,首先要确认,这个节点有没有通路,这里可以理解为,在之前深度优先搜索的过程中,该节点有没有被标记,如果被标记了,说明有路径可以被找到,代码如下所示:
bool DFS::hasPathTo(int v)
{
return isMarked(v);
}
然后我们需要一个存储路径的容器,让它在深度优先搜索的过程当中,记录下来下面的内容:
记录一下某个顶点第一次被访问,是被哪个顶点访问的
这里包含两个要素
(1)某顶点第一次被访问了。
(2)该顶点被哪个顶点第一次访问。
所以我们要存储的就是两个点而已,第一个点就是被第一次访问的一个顶点,第二个点就是访问 第一次被访问顶点 的点,比如2顶点第一次被1访问了,那么就可以记录下来一个数值对(2, 1),其他点同理。
最终我们会得到一颗树结构,这颗树结构会清晰的展示从起点开始通往每个顶点的路径是怎样的情况,在本篇中,起点为0,最终记录下来的树结构如下图所示:
本篇使用map来存储这颗树,记录路径的方法很简单,在某节点第一次被访问的时候,记录它就可以,代码如下所示:
void DFS::dfs(undirect_Graph uGraph, int v)
{
cout << "----------check " << v << " -----------" << endl;
marked[v] = true;
count++;
list<Vertex*> V_uGraphVertexSet = uGraph.getGraphVertexSet()[v]->neighbours;
list<Vertex*>::iterator iter = V_uGraphVertexSet.begin();
for(iter; iter != V_uGraphVertexSet.end(); iter++)
{
if(!isMarked((*iter)->value))
{
cout << "---------- " << (*iter)->value << " is not marked -----------" << endl;
EdgeTo.insert(pair<int, int>((*iter)->value, v)); //记录路径
dfs(uGraph, (*iter)->value);
}
}
}
我们可以通过这颗树,来获取通往某顶点的路径,代码如下所示:
int DepthFirstGraphForSearchPaths::getLastVertex(int v)
{
auto iter = EdgeTo.find(v);
if(iter != EdgeTo.end())
{
return iter->second;
}
return -1;
}
stack<int> DepthFirstGraphForSearchPaths::pathStore(int v)
{
stack<int> pathstore;
if(!isMarked(v)) return stack<int>();
for(int x = v; x != getStartPoint(); x = getLastVertex(x))
{
pathstore.push(x);
}
pathstore.push(getStartPoint());
return pathstore; //返回路径栈,方便之后读取路径信息
}
测试代码:
string path = "graph_initial_1.txt";
undirect_Graph graph(path);
graph.print_allVertexAndEdge();
// DepthFirstGraph dfg(graph, 0);
// cout << dfg.getCount() << endl;
int startPoint = 0;
DepthFirstGraphForSearchPaths dffsp(graph, startPoint);
dffsp.printPaths();
for(int v = 4; v < graph.countOfVertex(); v++)
{
auto x = dffsp.pathStore(v);
while(!x.empty())
{
cout << x.top() << endl;
x.pop();
}
break;
}
可以看到这里查找的是通往4的路径 0 - 5 - 3 - 2 - 4