图的存储方式有两种:邻接矩阵和邻接表。

邻接矩阵

用二维数组来表示顶点之间是否存在边,边的权重为多少。对于无向图来说,邻接矩阵是一个对称矩阵。缺点是邻接矩阵虽然比较好写但是需要开辟一个二维数组,如果顶点数量太多,容易超过题目的内存限制。

邻接表

把同一个顶点的所有出边都放在一个列表中,那么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){}
};

将产生图函数封装成类,采用的是邻接矩阵的方式。图的构建过程如下所示:

  1. 实例化一个Graph对象,对数组按行遍历,取出每行数据对应的权重、from和to结点
  2. 判断当前图中的from和to结点是否存在,不存在则需要创建节点
  3. 用form和to结点创建边
  4. 丰富form结点的指向结点to、边、出度,丰富to结点的入度。
  5. 把建好的边添加到图里的边
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++实现相关推荐

  1. 图:BFS/DFS java实现

    上一篇博文介绍了BFS和DFS的原理,现在给出其JAVA代码实现: BFS就是维护一个队列,先依次访问起始点相邻的节点,入队,再访问相邻节点的相邻节点,依次入队出队. DFS就是利用递归+回溯,直到递 ...

  2. LeetCode 695. 岛屿的最大面积(图的BFS/DFS)

    文章目录 1. 题目 2. 解题 2.1 BFS广度优先搜索 2.2 DFS深度优先搜索 1. 题目 给定一个包含了一些 0 和 1的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直 ...

  3. LeetCode 133. 克隆图(图的BFS/DFS)

    1. 题目 给定无向连通图中一个节点的引用,返回该图的深拷贝(克隆).图中的每个节点都包含它的值 val(Int) 和其邻居的列表(list[Node]). class Node {public:in ...

  4. LeetCode 1020. 飞地的数量(图的BFS/DFS)

    文章目录 1. 题目 2. 解题 2.1 BFS 2.2 DFS 1. 题目 给出一个二维数组 A,每个单元格为 0(代表海)或 1(代表陆地). 移动是指在陆地上从一个地方走到另一个地方(朝四个方向 ...

  5. LeetCode 1254. 统计封闭岛屿的数目(图的BFS DFS)

    文章目录 1. 题目 2. 解题 2.1 DFS 2.2 BFS 1. 题目 有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 ). 我们从一块陆地出发,每次可以往 ...

  6. LeetCode 130. 被围绕的区域(图的BFS/DFS)

    文章目录 1. 题目 2. 解题 2.1 BFS 2.2 DFS 1. 题目 给定一个二维的矩阵,包含 'X' 和 'O'(字母 O). 找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' ...

  7. 图的遍历(DFS和BFS)

    图的遍历是指从图中某一顶点出发,访遍图中其余顶点,且使每一个顶点仅被访问一次. 一.深度优先遍历(Depth First Search) 假设给定图G的初态是所有顶点均未曾访问过.在G中任选一顶点v为 ...

  8. bfs dfs 搜索入门模板题

    bfs & dfs 题目链接:https://vjudge.net/contest/404511 1.最短路(bfs) (1)一维最短路 D - Catch That Cow 题目大意: 在一 ...

  9. leetcode 310. Minimum Height Trees | 310. 最小高度树(图的邻接矩阵DFS / 拓扑排序)

    题目 https://leetcode.com/problems/minimum-height-trees/ 题解 方法1:图的邻接矩阵 DFS(超时) 我一想,这不就是个图嘛,于是随手敲出一个 DF ...

最新文章

  1. quadTree 论文Real-Time Generation of Continuous吃透了
  2. 利用堆排序查找数组中第K小的元素方法
  3. Lake Counting POJ - 2386
  4. 大学生如何成功就业。
  5. 算法题存档20190127
  6. lucene学习5----Field类及辅助类说明
  7. webdriver高级应用- 操作日期控件
  8. Opencv之生成棋盘标定板
  9. python torchvision_pip install torchvision error:安装版本为0.4.1的torch后继续安装torchvision报错...
  10. Unicode编码在JavaScript中的作用是什么?
  11. 【下载一】NI 系列软件卸载工具
  12. java游戏编程:三路兵线,BOSS走位,代码和视频
  13. 北理工嵩天Python语言程序设计笔记(目录)
  14. 2000-2019年中国地级市人均GDP
  15. JS正则表达式匹配手机号
  16. Libuv 句柄优雅关闭
  17. 秒懂设计模式之组合模式(Composite Pattern)
  18. 2016谷歌应用商店 TOP20APPS
  19. html和spwht表示什么状态,sⅠrcse中文是什么意思
  20. linux 2 、Xshell连接Ubuntu

热门文章

  1. 直流电机驱动及低转速控制的实现
  2. java计算机毕业设计ssm+jsp成都美食推荐系统
  3. Python实现可以语音聊天的桌面宠物程序
  4. AMT49105:高度集成的 ASIL BLDC MOSFET驱动器IC
  5. 会员制营销以及E-mail营销的培训
  6. 12Python爬虫---Fiddler抓包工具使用
  7. linux环境变量应该配置在哪里?
  8. weui实现移动端地址三级联动,基于js
  9. 中国联通6G白皮书笔记
  10. 匈牙利命名法 介绍 淘汰分析及取舍