上一篇我们讲解了图遍历的深度优先搜索DFS,今天讲另外一个图的遍历方法,即广度优先搜索BFS, 广度优先搜索又叫宽度优先搜索,和树的按层遍历的方法是一样的,在便遍历时图和树的区别上一篇文章已经有所讲过不再复述。
广度优先搜索BFS:假设从图中某个顶点V出发, 在访问了V之后一次访问V的各个未曾访问过的邻接点,然后分别从这些邻接点出发,一次访问他们的邻接点;在遍历这些邻接点的时候必须遵从这样的规则:使得先被访问的顶点的邻接点必须先于后被访问的顶点的邻接点。就是说谁先来的谁的子孙就优先于后来的人的子孙进行访问遍历,在树中就是左子树的所有儿子比子树的儿子先被遍历。继续,重复上述过程直到没有可以被访问的顶点。若图为连通图此时所有顶点都已经被遍历过,但是当图为非联通图时,此时还有空余的顶点没有遍历,所有还需要查看是否有剩余未被标记的顶点,然后依次做为起点进行上述过程,直到图中全部顶点都被标记过为止。说白了BFS就是每次都选取相同最短步长的邻接点进行遍历,步长为1, 2, 3, 4……
按照BFS的搜索遍历,上述图的遍历顺序为 V1 V2 V3 V4 V5 V6 V7 V8, 从V1开始到V2,V3步长为1, 再到V4 V5 V6 V7 步长为2 到最后V8步长为3
typedef int EdgeType;
typedef int VertexType;
#define MAXVEERTEX 100
typedef struct edge // 边表结点
{
int ver_index; // 邻接顶点域
edge* next; // 下一个邻接顶点域
}EdgeNode;
typedef struct vertex // 顶点表结构
{
VertexType ver_noee; // 顶点信息
EdgeNode* first_vertex; // 顶点链表
}VertexNode, AdjList[MAXVEERTEX];
typedef struct graph
{
AdjList list;
bool mark[MAXVEERTEX];
int ver_num, edge_num;
}Graph_L;
typedef struct graph
{
bool mark[MAXVEERTEX];
int vertex_num, edge_num;
int map[MAXVEERTEX][MAXVEERTEX];
}Graph_M;
// 邻接矩阵实现BFS
void BFS_M(Graph_M* g)
{
int cur_ver;
int next_ver;
queue<int> Q; // BFS都要使用队列
// 初始化标记数组
for (int i = 0; i < g->vertex_num; i++)
{
g->mark[i] == false;
}
// 遍历寻找没有遍历过的顶点进行BFS
// 处理非连通图时才有用
for (int i = 0; i < g->vertex_num; i++)
{
if (g->mark[i] == false)
{
// 当前没有遍历的顶点作为起点进行BFS
// 当图为连通图时,后序代码只执行一次
Q.push(i);
g->mark[i] = true;
while(!Q.empty()) // 当前顶点入队
{
cur_ver = Q.front(); // 获取到最早选取入队的顶点
Q.pop();
// 依次处理当前顶点的邻接点进行入队
for (next_ver = 0; next_ver < g->vertex_num; next_veri++)
{
if (g->map[cur_ver][next_ver] == 1 && g->mark[next_ver] == false)
{
g->mark[next_ver] = true;
Q.push(next_ver); // 此时入队的都是步长为一的顶点
}
}
}
}
}
}
// 邻接表实现BFS
void BFS_L(Graph_L* g)
{
int cur_ver;
EdgeNode* next_ver = NULL;
queue<int> Q; // BFS都要使用队列
// 初始化标记数组
for (int i = 0; i < g->vertex_num; i++)
{
g->mark[i] == false;
}
// 遍历寻找没有遍历过的顶点进行BFS
// 处理非连通图时才有用
for (int i = 0; i < g->vertex_num; i++)
{
if (g->mark[i] == false)
{
// 当前没有遍历的顶点作为起点进行BFS
// 当图为连通图时,后序代码只执行一次
Q.push(i);
g->mark[i] = true;
while(!Q.empty()) // 当前顶点入队
{
cur_ver = Q.front(); // 获取到最早选取入队的顶点
Q.pop();
// 依次处理当前顶点的邻接点进行入队
next_ver = g->list[cur_ver].first_vertex;
while(next_ver)
{
if (g->mark[next_ver->ver_index] == false)
{
g->mark[next_ver->ver_index] = true;
Q.push(next_ver->ver_index);
}
next_ver = next_ver->next; // 找到下一个邻接点
}
}
}
}
}
BFS和DFS两种遍历算法,都是要遍历所有的顶点,所以时间复杂度是一样的;唯一的不同在于对于每个顶点的遍历顺序不一样,DFS是先找到一个顶点然后一直深入下去,步长从1,2,3,4,5到底再回溯回来5,4,3,2,1 重复上述过程,而BFS则是每次找到最短步长,当所有最短步长都遍历完了之后,在增加步长。两者在全图的搜索遍历上来说基本没有区别。不过在不同的遍历情况和需求下,二者还是有些区别,比如你是需要找到一个满足要求的顶点还是需要一个最优的顶点。
一般情况下DFS更适合目标明确,以能够找到目标为主不需要最优的遍历条件下,而BFS则因为每次都是最短步长开始,则更适合最优解的情况,比如最少走几步才能走出迷宫问题和一个走出迷宫的解就是两种不同的需求,前者BFS更具有优势在时间复杂度上,而后者多用DFS。你是要博览群书,不求甚解还是要精钻细研,偏僻入里都是看自己的选择了。。。。