图论基础
基本概念
1.图也是一种数据结构:是某类具体事物以及这些事物之间的联系。
2.图:顶点(vertex)和边(edge)组成。
顶点:具体事物。
边:具体事物之间的联系。
顶点的集合V,边的集合E,所以图记为G = (V,E)。
比如下图就是一个典型的图。。
图的分类
0.带权图
定义:边上带有权值的图。(不同问题中,权值意义不同,可以是距离、时间、价格、颜值 ……)
1.无向图
定义:边没有指定方向的图
相邻:两个顶点之间如果有边连接,那么就视为两个顶点相邻。
路径:相邻顶点的序列。
圈:起点和终点重合的路径。
连通图:任意两点之间都有路径连接的图。
度:顶点连接的边数叫做这个顶点的度。
树:没有圈的连通图。
森林:没有圈的非连通图
2.有向图
定义:边具有指定方向的图(有向图中的边又称为弧,起点称为弧头,终点称为弧尾
有向路径:相邻顶点的序列。
有向环:一条至少含有一条边且起点和终点相同的有向路径。
有向无环图(DAG):没有环的有向图。
度:一个顶点的入度与出度之和称为该顶点的度。
1)入度:以顶点为弧尾的边的数目称为该顶点的出度
2)出度:以顶点为弧头的边的数目称为该顶点的入度
图的存储方式
1.邻接矩阵
E[u][v] 表示的是顶点u与顶点v的关系。
如果顶点u和顶点v之间有边相连,
E[u][v] = 1;
否则
E[u][v] = 0;
如果是有权边,
E[u][v] = cost;
对于无向图
E[u][v] = E[v][u];
优劣性:
优点:可以在常数时间内判断两点之间是否有边存在。
缺点:表示稀疏图时,浪费大量内存空间。
2.邻接表
通过把“从顶点0出发有到顶点2,3,5的边”这样的信息保存在vector中来表示图
邻接表非常的常用,所以我会在例题一解释它的具体用法。(当然所有的例题我都会使用邻接表进行存图
图的遍历方式
1.深度优先搜索(Depth-First Search)
遍历类似于树的先根遍历,是树的先根遍历的推广。
定义:假设初始状态是图中所有顶点未曾被访问,则深度优先搜索可从图中某个顶点v出发,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
T1 有向图的 dfs
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 205;
vector<int> E[MAXN];
// 定义邻接表(edge)E
bool vis[MAXN];
// 记录是否访问过
void Addedge(int u, int v) {
// 存储边的信息
E[u].push_back(v); // 表示以u为起点的边中有一条边的终点是v
}
void dfs(int i) {
printf("%d ", i);
vis[i] = true;
sort(E[i].begin(), E[i].end());
// 因为要求字典序所以需要进行排序
for (int j = 0; j < E[i].size(); j++) {
// 枚举边
if (vis[E[i][j]] == false) {
dfs(E[i][j]); // 继续进行dfs
}
}
}
int main() {
int m, n;
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d %d", &u, &v);
E[u].push_back(v);
}
for(int i = 1; i <= n; i++) // 有可能不是连通图哦
if(vis[i] == false) // i没被访问过就dfs一次
dfs(i);
return 0;
}
无向图同理。。。
广度优先搜索(Breadth-First Search)
遍历类似于树的按层次遍历的过程。
定义:假设从图中某顶点v出发,在访问v之后依次访问v的各个未被访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。换句话说,广度优先搜索遍历图的过程是以v为起始点,由近至远,依次访问和v有路径相通且路径长度为1,2,…的顶点。
T2 有向图的 bfs
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 5005;
vector<int> E[MAXN];
// 邻接表
bool vis[MAXN];
// 记录是否被访问过
void Addedge(int u, int v) {
// 存图
E[u].push_back(v);
}
void BFS(int st) {
queue<int> q;
q.push(st);
// 初始节点进队
vis[st] = true;
printf("%d ", st);
while(!q.empty()) { // 如果队列里还有节点
int t = q.front();
// 拿出第一个
q.pop();
sort(E[t].begin(), E[t].end());
// 因为要求字典序所以需要进行排序
for(int i = 0; i < E[t].size(); i++) {
// 枚举边
if(vis[E[t][i]] == false) {
vis[E[t][i]] = true;
printf("%d ", E[t][i]);
q.push(E[t][i]);
// 终点进队
}
}
}
}
int main() {
int n, m;
scanf ("%d %d", &n, &m);
for(int i = 1; i <= m; i++) {
int u, v;
scanf ("%d %d", &u, &v);
Addedge(u, v);
}
for(int i = 1; i <= n; i++) // 有可能不是连通图哦
if(vis[i] == false) // i没被访问过就dfs一次
BFS(i);
return 0;
}
无向图同理。。。
突破极限,一旦放弃了就意味着结束,说不定身体里还隐藏着连自己都没有察觉到的力量,不要被所谓的极限所禁锢。