图模板、BFS、DFS的C++实现
图
图的存储方式有两种:邻接矩阵和邻接表。
邻接矩阵
用二维数组来表示顶点之间是否存在边,边的权重为多少。对于无向图来说,邻接矩阵是一个对称矩阵。缺点是邻接矩阵虽然比较好写但是需要开辟一个二维数组,如果顶点数量太多,容易超过题目的内存限制。
邻接表
把同一个顶点的所有出边都放在一个列表中,那么N个顶点就会有N个列表,这N个列表就是图G的邻接表,记为Adj[N]
每个结点会存放一条边的信息(括号外的数字是边的终点编号,括号内的数字是边权。
对于初学者来说,使用变长数组vector来表示邻接表更为简便。
如果邻接表只存放每条边的终点序号而不存放权重,则vector中的元素类型可以直接定义为int类型vector<int>Adj[N];
如果想添加一条3号结点到1号结点的有向边,那么就需要Adj[3].push_back(1);
如果邻接表中还需要存放边权,则vector的元素类型使用结点Node结构体,代码如下:
struct Node{int w;//边权int v;//边的终点序号
};
此时要添加一条3号结点到1号结点的权重为2的有向边,代码如下:
Node temp;
temp.w=2;
temp.v=1;
Adj[3].push_back(temp);
最快速的写法是给结构体提供构造函数,在添加过程中直接加入临时结点,代码如下:
struct Node{int w,v;Node(int _w,int _v):w(_w),v(_v){}
};
Adj[3].push_back(Node(2,1));
图的遍历
遍历方法一般有两种:深度优先搜索DFS和广度优先搜索BFS。
DFS
思想:沿着一条路径直到无法继续前进,才退回路径上离当前顶点最近的还存在未被访问的结点的岔路口,并继续访问那些分支顶点,直到完成遍历整个图。
实现:将经过的顶点设置为已访问,在下次递归遇到这个顶点的时候就不再处理,直到整个图的顶点都被标记为已访问。伪代码如下:
DFS(u){vis[u]=true;//设置u已被访问for(从u出发能到达的所有顶点v)if vis[v]==false //如果v未被访问DFS(v);//递归访问v
}
DFSTrave(G){ //遍历图G for(G的所有顶点u){ //对图G的所有顶点if vis[u]=false //如果u未被访问DFS(u); //访问u所在的连通块}
}
邻接矩阵DFS
const int MAXV=1000;
const int INF=1000000000;
int n,G[MAXV][MAXV];//n为顶点数,MAXV为最大顶点数
bool vis[MAXV]={false};//将所有结点的访问状态初始化为未访问void DFS(int n,int depth){//u为当前访问的结点符号,depth为深度vis[u]=true;//遍历从u出发可以到达的所有结点for(int v=0;v<n;v++){if(vis[v]==false&&G[u][v]!=INF){//如果v未被访问,且u可以到达vDFS(v,depth+1);}}
}
void DFSTrave(){for(int u=0;u<n;u++){//对每个顶点uif(vis[u]==false){//如果当前顶点u未被访问DFS(u,1);//访问每个顶点u所在的连通块,表示第一层}}
}
邻接表版本DFS
const int MAXV=1000;
const int INF=1000000000;
vector<int>Adj[MAXV];
int n;
bool vis[MAXV]={false};void DFS(int u,int depth){vis[u]=true;for(int i=0;i<Adj[u].size();i++){//遍历当前结点u后连接的所有出度点int v=Adj[u][i];if(vis[v]==false){DFS(v,depth+1);}}
}
void DFSTrave(){for(int u=0;u<n;u++){if(vis[u]==false){DFS(u,1);}}
}
两个结构的DFS区别主要是由于图的表示方法不同,所以在找某个结点下一层的其他结点时,使用邻接矩阵需要遍历所有的位置,查找未被访问的且与该点连接的点。而邻接表只需要查找当前结点后面连接的表即可,不必遍历所有结点。
BFS
思想:每次以扩散的方式访问顶点。从搜索的起点开始,不断地优先访问当前结点的邻居,也就是说,首先访问起点,然后依次访问起点尚未访问的邻居结点,然后根据访问起点邻居结点的先后顺序依次访问他们的邻居,直到找到解或者搜遍整个空间。
步骤:需要建立一个队列,将初始顶点加入队列,通过反复取出队首顶点,将该顶点可到达的未曾加入过队列的顶点全部入队,直到队列为空时遍历结束。伪代码如下:
BFS(u){queue q;inq[u]=true;while(q非空){for(从u出发可达到的所有顶点v){if(inq[v]==false){将v入队inq[v]=true;}}}
}
BFSTrave(G){for(G的所有顶点u){if(inq[u]==false){BFS(u);}}
}
邻接矩阵BFS
int n,G[MAXN][MAXN];
bool inq[MAXN]={false};
void BFS(int u){//遍历u所在的连通块queue<int> q;//定义队列q.push(u);//将初始点u入队inq[u]=true;//设置u已加入过队列while(!q.empty()){//只要队列非空int u=q.front();//取出队首元素q.pop();//将队首元素出队for(int v=0;v<n;v++){//如果u的邻接点v未曾加入过队列if(inq[v]==false&&G[u][v]!=INF){q.push(v);//将v入队inq[v]=true;//标记v为已加入过队列}}}
}
void BFSTrave(){for(int u=0;u<n;u++){if(inq[u]==false){BFS(u);}}
}
邻接表版本
vector<int> Adj[MAXV];
int n;
bool inq[MAXV]={false};
void BFS(int u){//遍历单个连通块queue<int> q;//定义队列q.push(u);//将初始结点入队inq[u]=true;//设置u已经加入过队列while(!q.empty()){//只要队列非空int u=q.front();//获取队首元素q.pop();//弹出队列for(int i=0;i<Adj[u].size();i++){//枚举从u出发可以到达的顶点int v=Adj[u][i];if(inq[v]==false){//如果v未曾入队q.push(v);//将v入队inq[v]=true;//设置为已加入过队列}}}
}
void BFSTrave(){//遍历图Gfor(int u=0;u<n;u++){//枚举所有顶点if(inq[u]==false){//如果顶点u未曾加入过队列BFS(q);//遍历u所在的连通块}}
}
图模板
Graph类使用map存放结点信息,使用set存放边的信息
class Graph{public:unorder_map<int,Node*>nodes;unorder_set<Edge*>edges;Graph(){};
};
Node类由五部分组成,分别是节点的值value、入度in、出度out、当前结点的下一个节点nexts、以及从当前结点出发的边edges
class Node{public:int value;int out;int in;vector<Node*>nexts;vector<Edge*>edges;Node(int val,int inn=0,int outt=0):value(val),in(inn),out(outt){}
};
Edge类由三部分组成,分别是权重weight、当前边的from和to结点
class Edge{public:int weight;Node* from;Node* to;Edge(int w,Node* f,Node* t):weight(w),from(f),to(t){}
};
将产生图函数封装成类,采用的是邻接矩阵的方式。图的构建过程如下所示:
- 实例化一个Graph对象,对数组按行遍历,取出每行数据对应的权重、from和to结点
- 判断当前图中的from和to结点是否存在,不存在则需要创建节点
- 用form和to结点创建边
- 丰富form结点的指向结点to、边、出度,丰富to结点的入度。
- 把建好的边添加到图里的边
class GraphGenerator{public:Graph graph;for(int i=0;i<matrix.size();i++){//循环读取二维数组中的信息int weight=matrix[i][0];int from=matrix[i][1];int to=matrix[i][2];//如果这两个点没在图中出现过,就初始化这个点if(graph.nodes.find(from)==graph.nodes.end()){graph.nodes[from]=new Node(from);}if(graph.nodes.find(to)==graph.nodes.end()){graph.nodes[to]=new Node(to);}//从图中取出这两个点,丰富点的信息Node* fromNode=graph.nodes[from];Node* toNode=graph.nodes[to];//将读取到边权重和两端信息初始化一个边对象Edge* newEdge=new Edge(weight,fromNode,toNode);//丰富fromnode的信息,包括指向的结点、边、出度增加fromNode->nexts.push_back(toNode);fromNode->edges.push_back(newEdge);fromNode->out++;//丰富toNode的信息,入度增加toNode->in++;//将生成的这条边添加到图对象的边集合成员对象中graph.edges.insert(newEdge);}return graph;
};
图搜索
void BFS(Node* head) {if (head == NULL)return;queue<Node*>q;unordered_set<Node*>s;//使用set来判断节点是否加入过队列q.push(head);//将首结点加入队列s.insert(head);while (!q.empty()) {//只要队列不为空,就一直循环Node* cur = q.front();//取当前队首元素,并弹出cout << cur->value << " ";q.pop();for (Node* n : cur->nexts) {//遍历该节点的所有相邻(下一层)节点if (s.find(n) == s.end()) {//如果这个结点没有加入过队列s.insert(n);//就将节点标记为已访问过,并入队q.push(n);}}}
}void DFS(Node* head) {if (head == NULL)return;stack<Node*>st;unordered_set<Node*>se;//将头结点入栈并记录st.push(head);se.insert(head);cout << head->value << " ";//只要栈不为空,就一直循环while (!st.empty()) {//弹出栈顶元素作为当前结点Node* cur = st.top();st.pop();//遍历当前结点的所有下一层结点,若没有下一层结点或者下一层结点都被访问了,那么就不断执行上两句程序//栈中结点不断弹出,直到找到当前结点的下一层结点或者栈弹空。for (Node* n : cur->nexts) {if (se.find(n) == se.end()) {//如果这个下一层结点未被访问过st.push(cur);//则继续深入,就需要将当前结点和下一层结点都压入栈中st.push(n);se.insert(n);//标记访问了这个下一层结点cout << n->value << " ";break;//找到了一个下一层结点,那么就退出当前循环,继续深入查找}}}
}
图模板、BFS、DFS的C++实现相关推荐
- 图:BFS/DFS java实现
上一篇博文介绍了BFS和DFS的原理,现在给出其JAVA代码实现: BFS就是维护一个队列,先依次访问起始点相邻的节点,入队,再访问相邻节点的相邻节点,依次入队出队. DFS就是利用递归+回溯,直到递 ...
- LeetCode 695. 岛屿的最大面积(图的BFS/DFS)
文章目录 1. 题目 2. 解题 2.1 BFS广度优先搜索 2.2 DFS深度优先搜索 1. 题目 给定一个包含了一些 0 和 1的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直 ...
- LeetCode 133. 克隆图(图的BFS/DFS)
1. 题目 给定无向连通图中一个节点的引用,返回该图的深拷贝(克隆).图中的每个节点都包含它的值 val(Int) 和其邻居的列表(list[Node]). class Node {public:in ...
- LeetCode 1020. 飞地的数量(图的BFS/DFS)
文章目录 1. 题目 2. 解题 2.1 BFS 2.2 DFS 1. 题目 给出一个二维数组 A,每个单元格为 0(代表海)或 1(代表陆地). 移动是指在陆地上从一个地方走到另一个地方(朝四个方向 ...
- LeetCode 1254. 统计封闭岛屿的数目(图的BFS DFS)
文章目录 1. 题目 2. 解题 2.1 DFS 2.2 BFS 1. 题目 有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 ). 我们从一块陆地出发,每次可以往 ...
- LeetCode 130. 被围绕的区域(图的BFS/DFS)
文章目录 1. 题目 2. 解题 2.1 BFS 2.2 DFS 1. 题目 给定一个二维的矩阵,包含 'X' 和 'O'(字母 O). 找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' ...
- 图的遍历(DFS和BFS)
图的遍历是指从图中某一顶点出发,访遍图中其余顶点,且使每一个顶点仅被访问一次. 一.深度优先遍历(Depth First Search) 假设给定图G的初态是所有顶点均未曾访问过.在G中任选一顶点v为 ...
- bfs dfs 搜索入门模板题
bfs & dfs 题目链接:https://vjudge.net/contest/404511 1.最短路(bfs) (1)一维最短路 D - Catch That Cow 题目大意: 在一 ...
- leetcode 310. Minimum Height Trees | 310. 最小高度树(图的邻接矩阵DFS / 拓扑排序)
题目 https://leetcode.com/problems/minimum-height-trees/ 题解 方法1:图的邻接矩阵 DFS(超时) 我一想,这不就是个图嘛,于是随手敲出一个 DF ...
最新文章
- quadTree 论文Real-Time Generation of Continuous吃透了
- 利用堆排序查找数组中第K小的元素方法
- Lake Counting POJ - 2386
- 大学生如何成功就业。
- 算法题存档20190127
- lucene学习5----Field类及辅助类说明
- webdriver高级应用- 操作日期控件
- Opencv之生成棋盘标定板
- python torchvision_pip install torchvision error:安装版本为0.4.1的torch后继续安装torchvision报错...
- Unicode编码在JavaScript中的作用是什么?
- 【下载一】NI 系列软件卸载工具
- java游戏编程:三路兵线,BOSS走位,代码和视频
- 北理工嵩天Python语言程序设计笔记(目录)
- 2000-2019年中国地级市人均GDP
- JS正则表达式匹配手机号
- Libuv 句柄优雅关闭
- 秒懂设计模式之组合模式(Composite Pattern)
- 2016谷歌应用商店 TOP20APPS
- html和spwht表示什么状态,sⅠrcse中文是什么意思
- linux 2 、Xshell连接Ubuntu