数据结构C++——图的遍历DFS和BFS
文章目录
一、深度优先搜索遍历(DFS)
①DFS的过程
深度优先搜索(DFS):遍历类似于树的先序遍历,是树的先序遍历的推广。
对于一个连通图,深度优先搜索遍历的过程如下:
(1) 从图中某个顶点v出发, 访问v。
(2) 找出刚访问过的顶点的第一个未被访问的邻接点, 访问该顶点。 以该顶点为新顶点,重复此步骤, 直至刚访问过的顶点没有未被访问的邻接点为止。
(3) 返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点, 访问该顶点。
(4) 重复步骤 (2) 和 (3), 直至图中所有顶点都被访问过,搜索结束。
②FirstAdjVex()函数的代码实现
FirstAdjVex()函数的代码实现
FirstAdjVex()函数代码实现的算法思路:
1:通过for循环遍历G的邻接表
2:若邻接表某个单元存放值为1,则表明形参结点和i号结点之间有边,则返回此邻接点下标i
3:若循环结束未找到相关邻接点,则返回-1
/*--------返回顶点的第一个邻接点编号-------*/
int FirstAdjVex(Graph G, int v) {
//返回v的第一个邻接表编号,没有则返回-1
for (int i = 0; i < G.vexnum; i++) {
if (G.arcs[v][i] == 1)//邻接表该处为1,表明形参结点和i号结点之间有边
return i;
}
return -1;
}
③NextAdjVex()函数的代码实现
NextAdjVex()函数的代码实现
NextAdjVex()函数代码实现的算法思路:
1:通过for循环从w+1位置处开始遍历图的邻接表
2:若找到某数组单元存放值为1,则返回此邻接点的下标i
3:若循环结束,没找到相应邻接点,则返回-1
/*---------返回v相对于w的下个邻接点--------*/
int NextAdjVex(Graph& G, int v, int w) {
//返回v相对于w的下一个邻接点,没有则返回-1
for (int i = w + 1; i < G.vexnum; i++) {
if (G.arcs[v][i] == 1)
return i;
}
return -1;
}
④深度优先搜索遍历连通图
深度优先搜索遍历连通图
深度优先搜索遍历连通图的算法思路:
1:定义访问数组,其初始值为"false"
2:从第v个顶点出发,每遍历一个结点则将其对应的访问数组单元置为"true",表明该结点已访问过
3:通过利用FirstAdjVex()和NextAdjVex()自定义函数,检查v顶点的所有邻接点
4:对v的尚未访问的邻接点w,递归调用DFS
/*----------深度优先搜索遍历连通图--------*/
bool visited[MVNum];//访问标志数组,其初值为"false"
void DFS(Graph G, int v) {
//从第v个顶点出发递归地深度优先遍历图G
cout << G.vexs[v]; visited[v] = true;//访问第v个顶点,并置访问标志数组相应分量值为true
for (int w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w)) {
//依次检查v的所有邻接点w,FirstAdjVex(G,v)表示v的第一个邻接点
//NextAdjVex(G,v,w)表示v相对于w的下一个邻接点,w>=0表示存在邻接点
if (!visited[w]) DFS(G, w);//对v的尚未访问的邻接顶点w递归调用DFS
}
}
⑤深度优先搜索遍历非连通图
深度优先搜索遍历非连通图
深度优先遍历非连通图的算法思路:
1:将标志数组的值均初始化为"false"
2:遍历顶点表,对标志数组值为"false"的顶点调用DFS();
/*----------深度优先搜索遍历非连通图---------*/
void DFSTraverse(Graph G) {
//对非连通图G,做深度优先遍历
for (int i = 0; i < G.vexnum; i++)
visited[i] = false;//访问标志数组初始化
for (int i = 0; i < G.vexnum; i++)
if (!visited[i]) DFS(G, i);//对尚未访问的顶点调用DFS
}
⑥采用邻接矩阵表示DFS
邻接矩阵表示DFS
采用邻接矩阵表示DFS的算法思路:
1:从第v个顶点出发啊深度优先搜索遍历图G
2:访问第v个顶点,并置访问标志数组相应的分量值为true,表明该结点已访问过
3:依次检查邻接矩阵v所在的行,若w未访问,则递归调用DFS_AM()
/*---------采用邻接矩阵表示图的深度优先搜索遍历---------*/
bool visited[MVNum];//定义标志数组
void DFS_AM(AMGraph G, int v) {
//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
cout<<G.vexs[v]<<" ";visited[v]=true;//访问第v个顶点,并置访问标志数组相应分量值为true
for(int w=0;w<G.vexnum;w++)//依次检查邻接矩阵v所在的行
{
if((G.arcs[v][w]!=0)&&(!visited[w])) DFS_AM(G,w);
//G.arcs[v][w]!=0表示w是v的邻接点,如果w未访问,则递归调用DFS_AM
}
}
⑦采用邻接表表示DFS
采用邻接表表示DFS
采用邻接表表示DFS的算法思路:
1:从v个顶点出发深度优先搜索遍历图G
2:定义指向边结点的指针p,令p指向v的边链表的第一个边结点
3:利用while循环遍历某顶点的边链表各结点信息,直到p指针指空为止
4:如果w未被访问则递归调用DFS_AL
/*---------采用邻接表表示图的深度优先遍历----------*/
bool visited[MVNum];
void DFS_AL(ALGraph G,int v)
{
//图G为邻接表类型,从第v个顶点出发深度优先搜索遍历图G
cout<<G.vertices[v].data<<" ";visited[v]=true;
ArcNode* p=new ArcNode;//定义指向边结点的指针
p=G.vertices[v].firstarc;//p指向v的边链表的第一个边结点
while(p!=NULL)//边结点非空
{
int w=p->adjvex;//表示w是v的邻接点
if(!visited[w]) DFS_AL(G,w);//如果w未访问,则递归调用DFS_AL
p=p->nextarc;//p指向下一个边结点
}
}
⑧测试的完整代码
测试的完整代码
由于第⑥、⑦两节需要用到的邻接矩阵和邻接表的知识笔者已经在以往的文章详细地介绍过,这里不再做过多赘述,也不再贴出完整测试源代码,测试代码在定义并创建邻接矩阵或邻接表和一些基本操作函数后,新建DFS_AM()函数或DFS_AL()函数并在主函数中调用即可。
对于邻接矩阵和邻接表的知识不熟悉的读者请移步此文章:数据结构C++——图的邻接矩阵和邻接表
以下给出主函数调用DFS()函数或DFSTraverse()函数的完整代码(用邻接矩阵表示):
#include<iostream>
#include<string>
using namespace std;
#define OK 1
#define MaxInt 32767//无穷大
#define MVNum 100//最大顶点数
typedef string VerTexType;//假设顶点的数据类型为字符串
typedef int ArcType;//假设边的权值类型为整型
/*---------图的邻接矩阵---------*/
typedef struct {
VerTexType vexs[MVNum];//顶点表
ArcType arcs[MVNum][MVNum];//邻接矩阵
int vexnum, arcnum;//图的顶点数和边数
}Graph;
/*--------确定顶点在图中的位置--------*/
int LocateVex(Graph G, VerTexType V) {
//确定V在G中的位置
for (int i = 0; i < G.vexnum; i++) {
if (G.vexs[i] == V) return i;
}
return -1;//未找到指定结点
}
/*--------返回顶点的第一个邻接点编号-------*/
int FirstAdjVex(Graph G, int v) {
//返回v的第一个邻接表编号,没有则返回-1
for (int i = 0; i < G.vexnum; i++) {
if (G.arcs[v][i] == 1)//邻接表该处为1,表明形参结点和i号结点之间有边
return i;
}
return -1;
}
/*---------返回v相对于w的下个邻接点--------*/
int NextAdjVex(Graph& G, int v, int w) {
//返回v相对于w的下一个邻接点,没有则返回-1
for (int i = w + 1; i < G.vexnum; i++) {
if (G.arcs[v][i] == 1)
return i;
}
return -1;
}
/*采用邻接矩阵表示法创建无向图*/
int CreateUDG(Graph& G) {
cin >> G.vexnum >> G.arcnum;
for (int i = 0; i < G.vexnum; i++) {//输入各顶点信息
cin >> G.vexs[i];
}
for (int i = 0; i < G.vexnum; i++)
for (int j = 0; j < G.vexnum; j++)
G.arcs[i][j] = 0;//将边结点表都初始化为0
for (int k = 0; k < G.arcnum; k++) {
VerTexType v1, v2;
cin >> v1 >> v2;//输入相连通的两个结点
int i = LocateVex(G, v1);
int j = LocateVex(G, v2);//找到两个结点的位置
G.arcs[i][j] = G.arcs[j][i] = 1;
//G.arcs[i][j] = 1;
}
return OK;
}
bool visited[MVNum];//访问标志数组,其初值为"false"
/*----------深度优先搜索遍历连通图--------*/
void DFS(Graph G, int v) {
//从第v个顶点出发递归地深度优先遍历图G
cout << G.vexs[v] << " "; visited[v] = true;//访问第v个顶点,并置访问标志数组相应分量值为true
for (int w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w)) {
//依次检查v的所有邻接点w,FirstAdjVex(G,v)表示v的第一个邻接点
//NextAdjVex(G,v,w)表示v相对于w的下一个邻接点,w>=0表示存在邻接点
if (!visited[w]) DFS(G, w);//对v的尚未访问的邻接顶点w递归调用DFS
}
}
/*----------深度优先搜索遍历非连通图---------*/
void DFSTraverse(Graph G) {
//对非连通图G,做深度优先遍历
for (int i = 0; i < G.vexnum; i++)
visited[i] = false;//访问标志数组初始化
for (int i = 0; i < G.vexnum; i++)
if (!visited[i]) DFS(G, i);//对尚未访问的顶点调用DFS
}
/*----------主函数----------*/
int main()
{
Graph G;
CreateUDG(G);
int v = 0;
DFS(G, v);
//DFSTraverse(G);
return 0;
}
测试结果
输入:
8 9
v1 v2 v3 v4 v5 v6 v7 v8
v1 v2
v1 v3
v2 v4
v2 v5
v3 v6
v3 v7
v4 v8
v5 v8
v6 v7
输出:
v1 v2 v4 v8 v5 v3 v6 v7
--------------------------------一道华丽的分割线-----------------------------------
二、广度优先搜索遍历
①BFS的过程
广度优先搜索遍历类似于树的按层次遍历的过程。
广度优先搜索遍历的过程如下。
(1) 从图中某个顶点v出发,访问v。
(2) 依次访问v的各个未曾访问过的邻接点。
(3) 分别从这些邻接点出发依次访问它们的邻接点并使 “先被访问的顶点的邻接点“ 先于“后被访问的顶点的邻接点” 被访问。重复步骤 (3) , 直至图中所有已被访问的顶点的邻接点都被访间到。
②广度优先搜索遍历连通图
广度优先搜索遍历连通图的算法思路
广度优先遍历连通图的算法思路
1:访问第v个顶点,并将相应的访问数组值置为true
2:定义辅助队列Q初始化并将其置空
3:将第v个顶点入队,开始循环
4:队头元素u出队后依次检查u的所有邻接点,输出u的尚未访问的邻接顶点,并将其对应的访问数组值置为true
5:将刚刚访问过的邻接点入队,
/*---------广度优先搜索遍历连通图---------*/
bool visited[MVNum];//访问标志数组,其初值为"false
void BFS(Graph G, int v) {
//按广度优先非递归遍历连通图
cout << G.vexs[v] << " "; visited[v] = true;//访问第v个顶点,并置访问标志数组相应分量值为true
SqQueue Q;
InitQueue(Q);//辅助队列Q初始化并置空
EnQueue(Q, v);//v进队
while (!QueueEmpty(Q)) {
QElemType u;
DeQueue(Q, u);//队头元素出队并置为u
for (int w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)) {
//依次检查u的所有邻接点w,FirstAdjVex(G,u)表示u的第一个邻接点
//NextAdjVex(G,u,w)表示u相对于w的下一个邻接点,w>=0表示存在邻接点
if (!visited[w]) {//w为u的尚未访问的邻接顶点
cout << G.vexs[w] << " ";
visited[w] = true;//访问w,并置访问标志数组相应分量值为true
EnQueue(Q, w);//w进队
}
}
}
}
③广度优先搜索遍历非连通图
广度优先搜索遍历非连通图
广度优先搜索遍历非连通图的算法思路
由于BFS遍历非连通图和DFS遍历非连通图的思路和代码几乎完全一样,只需将函数名修改即可,此处不再做算法分析。只贴出如下代码:
/*----------广度优先搜索遍历非连通图---------*/
void BFSTraverse(Graph G) {
//对非连通图G,做广度优先遍历
for (int i = 0; i < G.vexnum; i++)
visited[i] = false;//访问标志数组初始化
for (int i = 0; i < G.vexnum; i++)
if (!visited[i]) BFS(G, i);//对尚未访问的顶点调用BFS
}
④采用邻接矩阵表示BFS
采用邻接矩阵表示BFS
采用邻接矩阵表示BFS的算法思路:
1:创建并初始化一个队列
2:当队列不为空时执行循环体。队头元素出队并输出
3:遍历队头元素的邻接点
4:若其未被访问过,则将其访问数组的单位值置为true,并将其入队
/*----------采用邻接矩阵表示法表示BFS-----------*/
void BFS_AM(Graph& G, int v) {
SqQueue Q;
InitQueue(Q);//创建并初始化一个队列
visited[v] = true;//将传入的顶点号结点的访问数组单元值置为true,表明该结点已访问过
EnQueue(Q, v);//只要结点未被访问就将其进队
while (!QueueEmpty(Q))
{//队列不为空时执行循环体
int u = 0;
DeQueue(Q, u);
cout << G.vexs[u] << " ";
for (int i = 0; i < G.vexnum; i++) {
//遍历v号结点的邻接点
if (G.arcs[u][i] == 1 && visited[i] != true) {
//说明该i号结点是v号结点的邻接点,且i号结点未被访问过
visited[i] = true;//将i号结点的访问数组单位值置为true,表明已访问过
EnQueue(Q, i);//将i号结点入队
}
}
}
}
⑤采用邻接表表示BFS
采用邻接表表示广度优先搜索遍历
采用邻接表表示BFS:
1:将传入形参号的顶点的数据域输出,并将其标志数组单位值置为true
2:定义并初始化一个队列,定义指向某顶点的第一个边结点的指针
3:进入循环体,弹出队头元素,遍历此顶点的边结点,并将边结点对应的顶点的访问数组的单位值赋值为true,表明此顶点已访问过
4:将指针不断指向下一边结点,直到指到链表尾使指针被赋值NULL,循环结束
邻接表表示BFS的算法要涉及到队列和创建邻接表的知识,由于相关内容笔者已在之前的文章中介绍过了,这里不再过多赘述,完整代码定义队列和邻接表,并实现相关函数即可。笔者会给出BFS算法的具体实现代码和完整测试代码,以下是邻接表表示BFS的源代码:
/*-------采用邻接表表示广度优先搜素遍历------*/
bool visited[MVNum];
void BFS_AL(ALGraph G,int v)
{
cout<<G.vertices[v].data<<" ";//输出顶点数据域
visited[v]=true;//将该结点的访问数组单位值赋为true
SqQueue Q;
InitQueue(Q);//定义并初始化队列
EnQueue(Q,v);//将该顶点进队
ArcNode* w=new ArcNode;//定义指向边结点的指针
while(Q.q_front!=Q.q_rear)
{
int u=0;//定义接收出队元素的变量
DeQueue(Q,u);//出队
w=G.vertices[u].firstarc;//令指针指向顶点的第一个边结点
while(w!=NULL)
{
if(!visited[w->adjvex])//某边结点的访问数组的单位值为false
{
cout<<G.vertices[w->adjvex].data<<" ";//输出边结点数据域值
visited[w->adjvex]=true;//将边结点数据域所在顶点的访问数组单位值赋为true
EnQueue(Q,w->adjvex);//将边结点所表示的顶点进队
}
w=w->NextAdj;//将指针指向下一个边结点,直到指向最后一个结点将其赋值为空,循环结束
}
}
}
另外,此代码是笔者参考此文章敲出来的,即并非笔者纯原创,详情请移步此文章:邻接表无向图的广度优先遍历C/C++代码实现
有关队列和邻接矩阵、邻接表的知识在此文章,欢迎读者访问:
数据结构C++——图的邻接矩阵和邻接表.
数据结构C++——队列.
温馨提醒
笔者在写完代码后测试结果,却发现输出结果和使用邻接矩阵表示BFS输出的结果并不相同,笔者以为是代码的问题,后来看了一眼所创建的无向图和输出结果,发现输出结果完全符合BFS的逻辑。笔者想要告诉大家的是,BFS不同的代码最后测试输出结果可能并不相同,但可能并不代表逻辑或代码有问题,只要测试结果符合BFS的逻辑即可。
邻接表创建的无向图如下:
输入:
8 9
v1 v2 v3 v4 v5 v6 v7 v8
v1 v2
v1 v3
v2 v4
v2 v5
v3 v6
v3 v7
v4 v8
v5 v8
v6 v7
邻接矩阵表示BFS的测试结果
v1 v2 v3 v4 v5 v6 v7 v8
邻接表表示BFS的测试结果
v1 v3 v2 v7 v6 v5 v4 v8
⑥测试的完整代码
完整代码
#include<iostream>
#include<string>
using namespace std;
//#define OK 1
#define ERROR 0
#define MAXQSIZE 100
#define MVNum 100
typedef int Status;
typedef string VexType;
typedef int QElemType;
typedef struct ArcNode
{
int adjvex;
struct ArcNode* NextAdj;
}ArcNode;
typedef struct VNode
{
VexType data;
ArcNode* firstarc;
}VNode,AdjList[MVNum];
typedef struct
{
AdjList vertices;
int vexnum,arcnum;
}ALGraph;
typedef struct
{
QElemType* base;
int q_front;
int q_rear;
}SqQueue;
Status InitQueue(SqQueue& Q)
{
Q.base=new QElemType[MAXQSIZE];
Q.q_front=Q.q_rear=0;
return 1;
}
Status EnQueue(SqQueue& Q,QElemType e)
{
if((Q.q_rear+1)%MAXQSIZE==Q.q_front)
return ERROR;
Q.base[Q.q_rear]=e;
Q.q_rear=(Q.q_rear+1)%MAXQSIZE;
return 1;
}
Status DeQueue(SqQueue& Q,QElemType& e)
{
if(Q.q_front==Q.q_rear) return ERROR;
e=Q.base[Q.q_front];
Q.q_front=(Q.q_front+1)%MAXQSIZE;
return 1;
}
Status QueueEmpty(SqQueue& Q)
{
if(Q.q_front==Q.q_rear) return 1;
return ERROR;
}
int LocateVex(ALGraph G,VexType v)
{
for(int i=0;i<G.vexnum;i++)
{
if(v==G.vertices[i].data) return i;
}
return -1;
}
int CreateUDG(ALGraph& G)
{
cin>>G.vexnum>>G.arcnum;//输入顶点数和边数
for(int i=0;i<G.vexnum;i++)//初始化顶点表,将顶点的数据域赋值并将其指针域置空
{
cin>>G.vertices[i].data;//数据域赋值
G.vertices[i].firstarc=NULL;//指针域置空
}
for(int k=0;k<G.arcnum;k++)
{
VexType v1,v2;
cin>>v1>>v2;//输入依附于一条边的两个顶点
int i=LocateVex(G,v1);//找到v1的位置
int j=LocateVex(G,v2);//找到v2的位置
ArcNode* p1=new ArcNode;//定义指向第一个边结点的指针
p1->NextAdj=G.vertices[i].firstarc;//指针指向第一个边结点
G.vertices[i].firstarc=p1;
p1->adjvex=j;
ArcNode* p2=new ArcNode;
p2->NextAdj=G.vertices[j].firstarc;
p2->adjvex=i;
G.vertices[j].firstarc=p2;
}
return 1;
}
Status GetTop(SqQueue Q)
{
if(Q.q_front!=Q.q_rear)
return Q.base[Q.q_front];
}
/*-------采用邻接表表示广度优先搜素遍历------*/
bool visited[MVNum];
void BFS_AL(ALGraph G,int v)
{
cout<<G.vertices[v].data<<" ";//输出顶点数据域
visited[v]=true;//将该结点的访问数组单位值赋为true
SqQueue Q;
InitQueue(Q);//定义并初始化队列
EnQueue(Q,v);//将该顶点进队
ArcNode* w=new ArcNode;//定义指向边结点的指针
while(Q.q_front!=Q.q_rear)
{
int u=0;//定义接收出队元素的变量
DeQueue(Q,u);//出队
w=G.vertices[u].firstarc;//令指针指向顶点的第一个边结点
while(w!=NULL)
{
if(!visited[w->adjvex])//某边结点的访问数组的单位值为false
{
cout<<G.vertices[w->adjvex].data<<" ";//输出边结点数据域值
visited[w->adjvex]=true;//将边结点数据域所在顶点的访问数组单位值赋为true
EnQueue(Q,w->adjvex);//将边结点所表示的顶点进队
}
w=w->NextAdj;//将指针指向下一个边结点,直到指向最后一个结点将其赋值为空,循环结束
}
}
}
int main()
{
ALGraph G;
CreateUDG(G);
int v=0;
BFS_AL(G,v);
return 0;
}
三、总结
以上为笔者对于图的DFS和BFS的一些见解,希望初学者都能有所收获,有技术不到位的地方,还望各位大佬指正。
同时,笔者的个人主页还有数据结构中其他部分的一些见解与分析,后续数据结构的相关知识还将陆续更新,欢迎大家访问且共同学习!