图论基础涵盖顶点边集定义、有向无向图区分及连通性分析。存储方案包含邻接矩阵与邻接表(Vector/链式前向星)。遍历算法提供 DFS 与 BFS 的 C++ 实现,适配不同存储结构。
beaabea20 浏览
一、图的基本概念
图的定义
图 G 是由顶点集 V 和边集 E 组成,记为 G=(V,E),其中 V(G) 表示图 G 中顶点的有限非空集;E(G) 表示图 G 中顶点之间的关系(边)集合。若 V={v1,v2,…,vn},则用 |V| 表示图 G 中顶点的个数,也称图 G 的阶,E={(u,v)|u∈V,v∈V},用 |E| 表示图 G 中边的条数。
顶点 v 的度是指与它相关联的边的条数,记作 deg(v)。由该顶点发出的边称为顶点的出度,到达该顶点的边称为顶点的入度。
无向图中,顶点的度等于该顶点的入度(indeg)和出度(outdev),即 deg(v)=indeg(v)=outdeg(v)。
有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点 v 的入度 indeg(v) 是以 v 为终点的有向边的条数,顶点 v 的出度 outdeg(v) 是以 v 为起始点的有向边的条数,deg(v)=indeg(v)+outdeg(v)。
#include<iostream>#include<cstring>usingnamespace std;
constint N = 1010; // 结点个数、边的个数int n, m; // i 到 j 有一条权值为 edges[i][j] 的边int edges[N][N];
intmain(){
memset(edges, -1, sizeof edges);
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= n; i++){
int a, b, c;
cin >> a >> b >> c; // a - b 有一条边,权值为 c
edges[a][b] = c;
// 如果是无向边,需要反过来再存一下
edges[b][a] = c;
}
return0;
}
#include<iostream>#include<vector>usingnamespace std;
// <与哪条边相连,该边的权值>typedef pair<int,int> PII;
constint N = 1e5+10; // 结点个数、边的个数int n, m;
// 结点 i 到结点 edges[i].first 有一条权值为 edges[i].second 的边
vector<PII> edges[N];
intmain(){
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c; // a 和 b 之间有一条边,权值为 c
edges[a].push_back({b, c});
// 如果是无向边,需要反过来再存一下
edges[b].push_back({a, c});
}
return0;
}
链式前向星
和树的存储一模一样,只不过如果存在边权的话,我们多创建一维数组 w,用来存储边的权值即可。
#include<iostream>usingnamespace std;
constint N = 1e5+10; // 链式前向星int h[N], e[N * 2], ne[N * 2], w[N * 2], id;
int n, m;
// 其实就是把 b 头插到 a 所在的链表后面voidadd(int a, int b, int c){
id++;
e[id] = b;
w[id] = c; // 多存一个权值信息
ne[id] = h[a];
h[a] = id;
}
intmain(){
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c; // a 和 b 之间有一条边,权值为 cadd(a, b, c);
add(b, a, c);
}
return0;
}
数组含义
h[N]:表头数组,h[a] 表示从节点 a 出发的第一条边在 e 数组中的下标。初始值为 -1(表示没有边)。
e[N2]:存储每条边指向的终点节点编号。
ne[N2]:存储同一起点的下一条边的下标,用于构成链表。
w[N*2]:存储每条边的权值。
id:全局计数器,用于分配边的下标。
add 函数
这个函数的作用是在图中添加一条从 a 指向 b,权值为 c 的有向边。它采用'头插法'将新边插入到以 a 为起点的链表头部:
id++:为新边分配一个唯一的下标。
e[id] = b:记录这条边指向的终点是 b。
w[id] = c:记录这条边的权值是 c。
ne[id] = h[a]:让新边的 ne 指针指向原来的表头,建立链表连接。
h[a] = id:更新表头,让新边成为从 a 出发的第一条边。
main 函数
首先读入节点数 n 和边数 m。
然后循环 m 次,每次读入一条边的两个端点 a、b 和权值 c。
因为是无向图,所以需要调用两次 add 函数:
add(a, b, c):添加一条从 a 到 b 的边。
add(b, a, c):添加一条从 b 到 a 的边,以表示双向连通。
// 邻接矩阵#include<iostream>#include<cstring>#include<queue>usingnamespace std;
constint N = 1010;
int n, m;
int edges[N][N];
bool st[N]; // 标记哪些点已经访问过voiddfs(int u){
cout << u << endl;
st[u] = true;
// 遍历所有孩子for(int v = 1; v <= n; v++){
// 如果存在 u->v 的边,并且没有遍历过if(edges[u][v] != -1 && !st[v]){
dfs(v);
}
}
}
intmain(){
memset(edges, -1, sizeof edges);
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c; // a - b 有一条边,权值为 c
edges[a][b] = c;
// 如果是无向边,需要反过来再存一下
edges[b][a] = c;
}
return0;
}
// 邻接表——vector 数组#include<iostream>#include<vector>#include<queue>usingnamespace std;
typedef pair<int,int> PII;
constint N = 1e5+10;
int n, m;
vector<PII> edges[N];
bool st[N]; // 标记哪些点已经访问过voiddfs(int u){
cout << u << endl;
st[u] = true;
// 遍历所有孩子for(auto& t : edges[u]){
// u->v 的一条边,权值为 wint v = t.first, w = t.second;
if(!st[v]){
dfs(v);
}
}
}
intmain(){
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c; // a 和 b 之间有一条边,权值为 c
edges[a].push_back({b, c});
// 如果是无向边,需要反过来再存一下
edges[b].push_back({a, c});
}
return0;
}
// 邻接表——链式向前星#include<iostream>#include<queue>usingnamespace std;
constint N = 1e5+10; // 链式前向星int h[N], e[N * 2], ne[N * 2], w[N * 2], id;
int n, m;
// 其实就是把 b 头插到 a 所在的链表后面voidadd(int a, int b, int c){
id++;
e[id] = b;
w[id] = c; // 多存一个权值信息
ne[id] = h[a];
h[a] = id;
}
bool st[N];
voiddfs(int u){
cout << u << endl;
st[u] = true;
// 遍历所有的孩子for(int i = h[u]; i; i = ne[i]){
// u->v 的一条边int v = e[i];
if(!st[v]){
dfs(v);
}
}
}
intmain(){
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c; // a 和 b 之间有一条边,权值为 cadd(a, b, c);
add(b, a, c);
}
return0;
}
BFS
// 临接矩阵#include<iostream>#include<cstring>#include<queue>usingnamespace std;
constint N = 1010;
int n, m;
int edges[N][N];
bool st[N]; // 标记哪些点已经访问过voidbfs(int u){
queue<int> q;
q.push(u);
st[u] = true;
while(q.size()){
auto a = q.front();
q.pop();
cout << a << endl;
for(int b = 1; b <= n; b++){
if(edges[a][b] != -1 && !st[b]){
q.push(b);
st[b] = true;
}
}
}
}
intmain(){
memset(edges, -1, sizeof edges);
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c; // a - b 有一条边,权值为 c
edges[a][b] = c;
// 如果是无向边,需要反过来再存一下
edges[b][a] = c;
}
return0;
}
// 邻接表——vector 数组#include<iostream>#include<vector>#include<queue>usingnamespace std;
typedef pair<int,int> PII;
constint N = 1e5+10;
int n, m;
vector<PII> edges[N];
bool st[N]; // 标记哪些点已经访问过voidbfs(int u){
queue<int> q;
q.push(u);
st[u] = true;
while(q.size()){
auto a = q.front();
q.pop();
cout << a << endl;
for(auto& t : edges[a]){
int b = t.first, c = t.second;
if(!st[b]){
q.push(b);
st[b] = true;
}
}
}
}
intmain(){
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c; // a 和 b 之间有一条边,权值为 c
edges[a].push_back({b, c});
// 如果是无向边,需要反过来再存一下
edges[b].push_back({a, c});
}
return0;
}
// 邻接表——链式向前星#include<iostream>#include<queue>usingnamespace std;
constint N = 1e5+10; // 链式前向星int h[N], e[N * 2], ne[N * 2], w[N * 2], id;
int n, m;
// 其实就是把 b 头插到 a 所在的链表后面voidadd(int a, int b, int c){
id++;
e[id] = b;
w[id] = c; // 多存一个权值信息
ne[id] = h[a];
h[a] = id;
}
bool st[N];
voidbfs(int u){
queue<int> q;
q.push(u);
st[u] = true;
while(q.size()){
auto a = q.front();
q.pop();
cout << a << endl;
for(int i = h[a]; i; i = ne[i]){
int b = e[i], c = w[i];
if(!st[b]){
q.push(b);
st[b] = true;
}
}
}
}
intmain(){
cin >> n >> m; // 读入结点个数以及边的个数for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c; // a 和 b 之间有一条边,权值为 cadd(a, b, c);
add(b, a, c);
}
return0;
}