图论基本定理(握手定理):
(无向图)结点度数和 = 边数两倍
(有向图)结点入度与出度的和 = 边数
图的基本概念
顶点:图中的一个点,一个边的两头的顶点称为相邻的顶点
边:连接两个顶点的线段叫做边
度数:由一个顶点出发,有几条边就称该顶点有几度,或者该顶点的度数是几
出度:由一个顶点出发的边的总数
入度:指向一个顶点的边的总数
头尾:有向图,一条边的出发点称为头,指向点称为尾
奇点:度数为奇数的顶点
偶点:度数为偶数的顶点
路径:通过边来连接,按顺序的从一个顶点到另一个顶点中间经过的顶点集合
简单路径:没有重复顶点的路径
环:至少含有一条边,并且起点和终点都是同一个顶点的路径
简单环:不含有重复顶点和边的环
有向路径:图中一组顶点满足从其中任意一个顶点出发,都存在一条有向边指向这组顶点中的另一个
有向环:至少含有一条边的起点和终点都是同一个顶点的一条有向路径
简单有向环:一条不含有重复顶点和边的环
路径或环的长度就是路径和环包含的边数
通路:两点间连通的路径
基本通路(路径):没有重复出现的结点的通路
连通的:当从一个顶点出发可以通过至少一条边到达另一个顶点,我们就说这两个顶点是连通的
连通图:图中从任意顶点均存在一条边可以到达另一个任意顶点,我们就说这个图是个连通图
连通分量:图中连通的顶点与边的集合
权和网:在图的边给出相关的数,成为权,带权图一般成为网
最短路径:长度最短的一条通路
完全图:任何两点顶点之间都有边(弧)相连
稀疏图、稠密图:边(弧)很少的图,图中每个顶点的度数不高称为稀疏图,反之为稠密图
无环图:是一种不包含环的图
二分图:可以将图中所有顶点分为两部分的图
欧拉通路(回路):通过每条边一次且仅一次,而且走遍每个结点的通路
欧拉图:含欧拉通路的图
哈密顿通路(回路):通过每个结点一次且仅一次
哈密顿图:含哈密顿通路的图
图的存储
邻接矩阵
scanf("%d", &n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
scanf("%d", &a[i][j]);
邻接表
struct psx{int y, v, next} e[];
int lin[], len = 0;
void insert(int xx, int yy, int vv) {
e[++len].next = lin[xx];
lin[xx] = len;
e[len].y = yy;
e[len].v = vv;
}
void init() {
scanf("%d%d", &n, &m);
memset(e, 0, sizeof(e));
memset(lin, 0, sizeof(lin));
for(int i = 1; i <= n; i++) {
int xx, yy, vv;
scanf("%d%d%d", &xx, &yy, &vv);
insert(xx, yy, vv);
insert(yy, xx, vv);
}
}
边表
struct psx{int x, y, v} e[];
void init() {
scanf("%d%d", &n, &m);
memset(e, 0, sizeof(e));
for(int i = 1; i <= n; i++)
scanf("%d%d%d", &e[i].xx, &e[i].yy, &e[i].vv);
}
图的遍历
深宽搜(基础概念,大佬可跳过不看)
BFS
邻接矩阵代码
void bfs(int k) {
int head = 0, tail = 1;
q[tail] = k;
while(head++ < tail)
for(int i = 1; i <= n; i++)
if(a[q[head]][i] && !vis[i]) {
vis[i] = 1;
q[++tail] = i;
}
}
邻接表代码
void bfs(int k) {
int head = 0, tail = 1;
q[tail] = k;
while(head++ < tail)
for(int i = lin[ q[head] ]; i ; i = e[i].next)
if(!vis[ e[i].y ]) {
vis[ e[i].y ] = 1;
q[++tail] = e[i].y;
}
}
DFS
邻接矩阵代码
void dfs(int k) {
for(int i = 1; i <= n; i++)
if(a[k][i] && !vis[i]) vis[i] = 1, dfs(i);
}
邻接表代码
void dfs(int k) {
for(int i = lin[k]; i ; i = e[i].next)
if(!vis[e[i].y]) vis[e[i].y] = 1,dfs(e[i].y);
}
拓扑搜索
检查有向图是否存在回路
往拓扑序列中放入入度为
0
0
0的点,删去与它相连的边后,再往拓扑序列中放入入度为
0
0
0的点
…
…
……
……
如果拓扑序列中放入的元素数量小于连通图中顶点的数量,则存在环
scanf("%d%d", &x, &y); deg[y]++;//输入时记录一下入度
int head=0, tail=0;
for(int i = 1; i <= n; i++)
if(!deg[i]) q[++tail] = i;
//邻接表版本
while(head++ < tail)
for(int i = lin[q[head]]; i; i = e[i].next) {
deg[e[i].y]--;
if(!deg[e[i].y]) q[++tail] = e[i].y;
}
//邻接矩阵版本
while(head++ < tail)
for(int i = 1; i <= n; i++)
if(a[q[head]][i]) {
deg[i]--;
if(!deg[i]) q[++tail] = i;
}
图的传递闭包
判断无向图的连通性
判断
i
i
i到
j
j
j是否有路径
1.
1.
1.结点
i
i
i到结点
j
j
j有边
2.
2.
2.存在结点
k
k
k满足结点
i
i
i到结点
k
k
k有边,且结点
k
k
k到结点
j
j
j有边,则结点
i
i
i到结点
j
j
j有边
for(int i = 1; i <= n; i++) can[i][i] = 1;
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
can[i][j] ||= (can[i][k] && can[k][j]);
任意两点间的最短路
Floyed算法
根据图的传递闭包的原理,复杂度O(n 3 ^{3} 3)
初始化
dis[i][i] = 0;
dis[i][j] = 边权;(有边相连)
dis[i][j] = 正无穷;(无边相连)
算法
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(dis[i][k] + dia[k][j] < dia[i][j])
dia[i][j] = dis[i][k] + dia[k][j];
输出最短路径
p
a
t
h
[
i
]
[
j
]
path[i][j]
path[i][j]记录
i
i
i到
j
j
j的最短路径中
j
j
j的前驱结点
初始化
path[i][j] = i;(有边相连)
path[j][i] = j;(有边相连)
记录
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(dis[i][k] + dia[k][j] < dia[i][j]){
dia[i][j] = dis[i][k] + dia[k][j];
path[i][j] = k;
}
输出
void dfs(int i, int j){
if(path[i][j] > 0) {
dfs(i, path[i][j]);
cout<<path[i][j]<<" ";
dfs(path[i][j], j);
}
}
单源最短路径——Dijkstra(迪杰斯特拉算法)
如果源点到某个点 x x x的距离是到其他点的距离的最小值,那么到点 x x x的最短距离
单源,非负
用集合1表示已知点,用集合2表示未求点
则1中最初只有start一个点,集合2中有其他n-1个点
1.在集合2中找到到start距离最近的顶点k,距离=d[k]
2.把顶点k加到
void dijkstra(int st) {
for(int i = 1; i <= n; i++) dis[i] = a[st][i];
memset(vis, 0, sizeof(vis));
vis[st] = 1;
dis[st] = 0;
for(int i = 1; i < n; i++){
int minn = 99999999, k = 0;
for(int j = 1; j <= n; j++)
if(!vis[j] && dis[j] < minn) minn = dis[j], k = j;
if(k == 0) return;
vis[k] = 1;
for(int j = 1; j <= n; j++)
if(!vis[j] && dis[k]+a[c][j] < dis[j])
dis[j] = dis[k] + a[k][j];
}
}