博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
图论总结
阅读量:4568 次
发布时间:2019-06-08

本文共 8805 字,大约阅读时间需要 29 分钟。

这是一个图论总结。

图的概念

·图:由一些点和一些边组成的东西

·有向图:边有方向(单向连通)的图
·无向图:边无方向(双向连通)的图
·入度:有多少边连入这个点
·出度:有多少边连出这个点
·DAG:有向无环图

建图

邻接矩阵

用一个二维数组表示两个点之间有没有连边

s[x][y] = dis; //x和y之间有一条长度为dis的边

邻接表

用一个结构体记录所有的边,同时保存以这条边为起点的上一条边的编号,然后保存所有起点的最后一条边即可。

struct Edge{    int next, to, dis;}e[MAXN * MAXN];int head[MAXN], num;inline void Add(int from, int to, int dis){    e[++num].to = to;    e[num].dis = dis;    e[num].next = head[from];    head[from] = num;}

图的遍历

·DFS遍历

·BFS遍历

拓扑排序

·定义:拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

1、每个顶点出现且只出现一次。
2、若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

·求法:先把所有入度为\(0\)的点加入队列。当队列不为空时,取出队头并输出,删掉所有以该点为起点的边,并把所有新的入度为\(0\)的点加入队列。重复执行。

queue 
q;for(int i = 1; i <= n; ++i) if(!in[i]) //in记录入度 q.push(i);while(!q.empty()){ int now = q.front(); q.pop(); printf("%d ", now); for(int i=head[now];i;i=e[i].next) if(!(--in[e[i].to])) q.push(e[i].to);}

最短路

单源最短路

· Dijkstra

创建一个队列,保存“已经确定最短路”的点的集合。初始时队列里只有源点,除源点外所有点的距离赋值正无穷。
当集合不为空时,取出集合内距离最短的一个点,移除集合,枚举这个点连的所有边,如果这个点的距离加上这条边的距离比到的那个点小,那么更新这条边到的那个点的距离,并把它加入集合。重复执行。使用堆优化后时间复杂度\(O(n\log n)\)。适用于稠密图。

//洛谷 P4779 【模板】单源最短路径(标准版)#include 
#include
#include
#include
using namespace std;inline int read(){ int s = 0, w = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-')w = -1;ch = getchar();} while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0',ch = getchar(); return s * w;}const int MAXN = 100010;const int MAXM = 200010; struct Node{ int next, to, dis;}e[MAXM];int head[MAXN], num;ll dis[MAXN];bool v[MAXN];void Add(int from, int to, int dis){ e[++num].to = to; e[num].next = head[from]; e[num].dis = dis; head[from] = num;}int n, m, s;int a, b, c;struct cmp{ bool operator () (int a, int b){ return dis[a] > dis[b]; }};priority_queue
, cmp> q;int main(){ n = read(); m = read(); s = read(); for(i = 1; i <= m; ++i) a = read(), b = read(), c = read(), Add(a, b, c); for(i = 1; i <= n; ++i) dis[i] = INF; dis[s] = 0; q.push(s); while(!q.empty()){ int now = q.top(); q.pop(); if(v[now]) continue; v[now] = true; for(int i = head[now];i;i = e[i].next){ if(!v[e[i].to] && dis[e[i].to] > dis[now] + e[i].dis) dis[e[i].to] = dis[now] + e[i].dis, q.push(e[i].to); } } for(i = 1; i <= n; ++i) printf("%lld ", dis[i]); return 0;}

· SPFA

创建一个队列,初始时队列里只有源点。当队列不为空时,取出队头,和\(Dijkstra\)一样,枚举所有点,如果能更新距离,更新距离,如果更新的点不在队列里,则加入队列。重复执行。
时间复杂度\(O(ne)\),其中\(e\)为平均每个点进队列的次数,一般图很小,适用于稀疏图,但遇到稠密图时间复杂度会退化为\(O(nm)\)。还有就是不能跑有负环的图,因此也可以用来判断负环。

//SPFAqueue 
q;q.push(s); //s为源点 for(int i = 1; i <= n; ++i) dis[i] = INF;dis[s]=0;while(!q.empty()){ int now = q.front(); flag[now] = 0; q.pop(); for(int i = head[now]; i; i = e[i].next){ if(dis[e[i].to] > dis[now] + e[i].dis){ dis[e[i].to] = dis[now] + e[i].dis; if(!flag[e[i].to]) q.push(e[i].to), flag[e[i].to] = 1; } }}

多源最短路

·定义:求任意两点之间的最短路

·Floyd
初始时任意两点之间的距离为正无穷,任意点到自己的距离都为0,枚举所有“中介点”,再枚举所有点对,如果这两个点之间的距离大于起点到中介点再到终点的距离,则更新这两点之间的距离。时间复杂度\(O(n^3)\)。此算法还可推广到求任意两点之间的连通性。

//Floyd求多源最短路memset(dis, 127, sizeof dis);for(int i = 1; i <= n; ++i)   dis[i][i] = 0;for(int k = 1; k <= n; ++k)   for(int i = 1; i <= n; ++i)      for(int j = 1; j <= n; ++j)         dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);

Tarjan算法

割点

·定义:若将该点及其所有的边删去后,图中剩余点的连通性发生变化,则该点为割点,一张图可能存在多个割点。

·求法(Tarjan):
定义两个数组:\(dfn\)(时间戳),\(low\)(追溯值)
先看一下代码:

void Tarjan(int u,int f){    //vis[x]=1表示x为割点    dfn[u]=low[u]=++id;    int Max=0,son=0;    for(int i=head[u];i;i=e[i].next){       if(!dfn[e[i].to]){         Tarjan(e[i].to,f);         low[u]=min(low[u],low[e[i].to]);         if(low[e[i].to]>=dfn[u]&&u!=f) vis[u]=1;         if(u==f) ++son;       }       low[u]=min(low[u],dfn[e[i].to]);    }    if(u==f&&son>=2) vis[u]=1;}

由代码可知,没访问到一个点就给其分配一个时间戳,\(low\)可理解为“该点能回到的时间戳最小的点的时间戳”。

一个点\(u\)是割点,当且仅当该点不是根且存在一个有连边的点\(v\),满足
\[dfn[u]<=low[v]\]
或者该点是根且存在两个有连边的点满足上述条件。
根据定义,\(dfn[u]<=low[v]\)时,说明\(v\)无法到达比\(u\)更早访问的点,于是把\(x\)删除后,\(v\)\(u\)的父亲之间无法连通。但如果\(x\)本来就是根,则无法影响到\(v\),所以需要存在两个点满足此条件。

割点(桥)

·定义:若把该边删去后,图的连通性发生变化,则该边为桥。

·求法(Tarjan):
设一条边连接\(u,v\)两个点,当\(dfn[u]<low[v]\)时,该边为桥。
和割点一样,\(dfn[u]<low[v]\)说明\(v\)无法到达比\(u\)或比\(u\)更早的节点,删除该边后,\(u\)\(v\)“失联”,该边为桥。

强联通分量

·定义:一个图中任意两点能互相到达的极大的子图。“极大”就是指若该子图满足条件,且不存在一个包含这个子图的图满足条件,则该子图就是”极大”。

·求法(Tarjan):
Tarjan算法时把每个访问的点存到一个栈里,当对当前点的操作完成后判断,如果\(dfn[u]==low[u]\),则说明他的所有子节点无法到达比他更早的节点,那么这个点就是这个强联通分量的根,栈中所有在该点之上的点和该点共同构成一个强联通分量。
·缩点:把每个强联通分量看成一个点,重新连边即可。

void Tarjan(int u){    dfn[u] = low[u] = ++ID;    s.push(u);    vis[u] = 1;    for(int i = head[u]; i; i = e[i].next){       if(!dfn[e[i].to])         Tarjan(e[i].to), low[u] = min(low[u], low[e[i].to]);       else if(vis[e[i].to]) low[u] = min(low[u], dfn[e[i].to]);    }    if(dfn[u] == low[u]){      ++sum;      int now;      do{        now = s.top();        s.pop();        vis[now] = 0;          belong[now] = sum;   //属于哪个强联通分量      }while(now != u);    }}

·缩点的作用:把有环图变为无环图(树)

·定义:无向无环图

·链:树上两点之间的唯一路径

树的直径

·定义:树上最长的链

·求法:从任意一个点\(DFS\),找到离这个点最远的点,再从那个最远的点\(DFS\),找到离那个点最远的点,这两个最远的点之间的路径就是树的直径。

树的重心

最小生成树

·定义:一个\(n\)个点图中选\(n-1\)条边使得这张图变成一棵树,且使这\(n-1\)条边的权值和最小。

·求法:

Kruskal

把所有边按权值升序排序,依次枚举所有边,如果这条边连接的两个点没有连通,则把这条边连上,计数器加上这条边的权值,当连了\(n-1\)条边后,算法结束。判断连通性用并茶集。算法时间复杂度\(O(n\log n)\),其实就是排序的复杂度。

//洛谷 P3366 【模板】最小生成树#include 
#include
using std::sort;const int MAXN = 5010;const int MAXM = 200010;int f[MAXN];int find(int x){ return f[x] == x ? x : f[x] = find(f[x]);}void merge(int x, int y){ f[find(y)] = find(x);}struct Edge{ int x, y, z; bool operator < (const Edge A) const{ return z < A.z; }}e[MAXM];int n, m, ans;int main(){ scanf("%d%d", &n, &m); for(int i = 1; i <= n; ++i) f[i] = i; --n; for(int i = 1; i <= m; ++i) scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].z); sort(e + 1, e + m + 1); for(int i = 1; i <= m && n; ++i) if(find(e[i].x) != find(e[i].y)) merge(e[i].x, e[i].y), --n, ans += e[i].z; printf("%d\n", ans); return 0;}

次小生成树

最近公共祖先(LCA)

·定义:距离树上两个节点最近的同时是两个节点的祖先的点。

·求法:

倍增

\(f[u][i]\)表示节点\(u\)的第\(2^i\)个父亲, 用倍增转移状态。

查询\(LCA\)的时候先跳到同一高度,然后跳到同一高度就行。
具体看代码:

int LCA(int u, int v){    if(dep[u] > dep[v]) swap(u, v);    int cha = dep[v] - dep[u];    for(int i = 0; i <= LOGMAXN; ++i)       if(cha & (1 << i))         v = f[v][i];    if(u == v) return u;    for(int i = LOGMAXN; ~i; --i)       if(f[u][i] != f[v][i])         u = f[u][i], v = f[v][i];    return f[u][0];}

树链剖分(略)

Tarjan离线求LCA

点分治

·作用:快速枚举树上所有链

·方法:
1、找树的重心 -> 2、从重心开始\(DFS\) -> 3、找所有经过当前点的链 -> 4、对所有子树进行1、2、3号操作,

//洛谷 P2634 聪聪可可#include 
#include
#define re register#define il inlineusing std::max;using std::sort;inline int read(){ int s = 0, w = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { s = s * 10 + ch - '0'; ch = getchar(); } return s * w;}const int MAXN = 20010;struct Edge{ int next, to, dis;}e[MAXN << 1];int head[MAXN], num, n, size[MAXN], vis[MAXN], maxson[MAXN], Max, root, ans, s[5], p[5];inline void Add(int from, int to, int dis){ e[++num].to = to; e[num].next = head[from]; e[num].dis = dis; head[from] = num;}void getRoot(int u, int fa, int tot){ maxson[u] = 0; size[u] = 1; for(re int i = head[u]; i; i = e[i].next){ if(e[i].to != fa && !vis[e[i].to]){ getRoot(e[i].to, u, tot); size[u] += size[e[i].to]; maxson[u] = max(maxson[u], size[e[i].to]); } } maxson[u] = max(maxson[u], tot - size[u]); if(maxson[u] < Max) Max = maxson[u], root = u;}void getADB(int u, int fa, int dep){ if(dep % 3) ans += s[3 - dep % 3]; else ans += s[0] + 1; ++p[dep % 3]; for(re int i = head[u]; i; i = e[i].next) if(e[i].to != fa && !vis[e[i].to]) getADB(e[i].to, u, dep + e[i].dis);}void dfs(int u, int fa){ vis[u] = 1; s[0] = s[1] = s[2] = p[0] = p[1] = p[2] = 0; for(re int i = head[u]; i; i = e[i].next){ if(e[i].to != fa && !vis[e[i].to]) getADB(e[i].to, u, e[i].dis), s[0] += p[0], s[1] += p[1], s[2] += p[2], p[0] = p[1] = p[2] = 0; } for(re int i = head[u]; i; i = e[i].next){ if(!vis[e[i].to]){ Max = n; getRoot(e[i].to, u, size[e[i].to]); dfs(root, u); } }}int A, B, C;int gcd(int a, int b){ return !b ? a : gcd(b, a%b);}int main(){ Max = n = read(); for(re int i = 1; i < n; ++i){ A = read(); B = read(); C = read(); Add(A, B, C); Add(B, A, C); } getRoot(1, 0, n); dfs(root, 0); ans = (ans << 1) + n; int b = n * n; int GCD = gcd(ans, b); ans /= GCD; b /= GCD; printf("%d/%d\n", ans, b); return 0;}

二分图

二分图的判定

·黑白染色法

二分图匹配

·匈牙利算法

网络流

转载于:https://www.cnblogs.com/Qihoo360/p/9614159.html

你可能感兴趣的文章
树状数组
查看>>
【2019.8.14 慈溪模拟赛 T1】我不是!我没有!别瞎说啊!(notme)(BFS+DP)
查看>>
多任务--进程 及 进程间通信
查看>>
多线程/多进程+QProgressBar实现进度条
查看>>
多任务(进程)案例----- 拷贝文件夹
查看>>
Kotlin的快速入门
查看>>
底层原理
查看>>
21. Merge Two Sorted Lists
查看>>
创建数组
查看>>
dict使用
查看>>
ASP.NET MVC的帮助类HtmlHelper和UrlHelper
查看>>
02_ListActive中响应事件 并LogCat输出
查看>>
doubleclick adx note
查看>>
Celery框架
查看>>
[c#]asp.net开发微信公众平台(4)关注事件、用户记录、回复文本消息
查看>>
[转载,感觉写的非常详细]DUBBO配置方式详解
查看>>
linux Valgrind使用说明-内存泄漏
查看>>
Android在Eclipse上的环境配置
查看>>
面向对象(五)
查看>>
android平台下使用点九PNG技术
查看>>