bfs和dfs
特点与区别
深度优先搜索和广度优先搜索都是用来遍历一个图的,两种遍历方法的主要特点是
- 深度优先:不撞南墙不回头
- 广度优先:向外扩散
现在可能听起来很让人疑惑,接下来我们逐个来析:
深度优先搜索
深度优先搜索(Depth First Search):的主要思路是沿着一条路径一直走,直到走不动的时候再回头。什么时候叫走不动的时候呢,当访问到一个图中一个结点时,该结点已经没有任何的邻接的且未访问的结点时,此时可形象地称为走不动了,撞了南墙了。
深度优先搜索的实现往往和一种数据结构——栈联系在一起,具体思路如下:
- 设定一个遍历的起点,并将其入栈;
- 访问并弹出栈顶结点,将栈顶结点的邻接的且未访问的结点入栈;
- 重复步骤2,直到栈为空;
C++代码实现如下(图以邻接矩阵为例,以结点的序号来表示结点)
void dfs(int start)
{
stack<int>search_sta;
vector<bool>visited(size,false);
int temp_node;
search_sta.push(start); //起点入栈
visited.push_back(start); //标为已知
while(!search_sta.empty())
{
temp_node = search_sta.top(); //访问栈顶元素
search_sta.pop(); //栈顶元素出栈
for(int i = 0; i < size; i++)
{
if(edges[temp_node][i]&&visited[i]==false)
{
//将未访问的邻接点入栈并标为已访问
search_sta.push(i);
visited[i]=true;
}
}
}
}
同样的,我们也可以用递归的方式来实现dfs,事实上递归也是一种栈,代码如下:
void dfs_recursion(int start)
{
static vector<bool>visited(size,false);
visited[start]=true;
for(int i = 0; i < size; i++)
{
if(visited[i]==false&&edges[start][i])
{
dfs_recursion(i);
}
}
}
广度优先搜索
广度优先搜索(Breadth First Search)的主要思路是从起点开始,访问其所有邻接点,再依次访问这每个邻接点的所有邻接点(好像有点绕)。与深度优先搜索所不同的是,广度优先搜索对于每个结点和它的邻接点都尝试去访问。从直观上来看,bfs是一层一层的,第一层访问最近一圈的结点(假设是无权图),而后层数随着圈不断向外扩展。
广度优先搜索的实现往往和另一种数据结构——队列有关,具体思路如下:
- 设定一个遍历的起点,将起点入队;
- 访问队头结点并将其出队,将队头结点的所有邻接且未访问结点入队;
- 重复步骤2直到队为空;
C++实现代码如下(图用邻接表实现,用结点的序号代表结点):
void bfs(int start)
{
queue<int>search_que;
vector<bool>visited(size,false);
int temp_node;
search_que.push(start);
visited[start]=true;
while(!search_que.empty())
{
temp_node = search_que.front(); //访问队头元素
search_que.pop(); //队头元素出队
for(auto t : edges[temp_node]) //map<int,set<int>>edges
{
if(visited[t]==false)
{
//将未访问的邻接点入队并标为已知
search_que.push(t);
visited[t] = true;
}
}
}
}
在C++中,个人习惯用关联容器来实现图的邻接表,例如对于无权图,可用
map<node,set<node>>edges
来表示,以结点为键,以该结点的所有邻接点的集合为值。
而对于有权图,则可用
map<node,map<node,weight>>edges
来表示,例如edges[node1][node2]即为连接node1和node2两结点的边的权值。