零、前言

作者是 xxs ,图论学得不多,文章有错误还请指出。

一、图的存储与遍历

存储

存图有多种方法,都不复杂,很容易实现。

1.邻接矩阵

直接使用二维数组 graph[N][N] 来存,它虽然代码简单,查询较快,但是有时候很浪费空间,而且数据范围有较大的限制,并不常用。

2.邻接表

顾名思义,邻接表就是 每个节点值储存它直接可以到的其他节点

它的空间浪费比邻接矩阵小得多,但是,在找邻居的时候,要遍历它的整个邻接表,速度比邻接矩阵慢。

3.链式前向星

它其实就是用链表实现的邻接表。

主体一个 head[u] 数组,指向 u 的邻居存的地址。
和一个 struct Edge{int to,nxt}; ,其中, to 指邻居编号, nxt 指下一个邻居的地址。

具体代码如下:

点击查看代码
struct Edge{int nxt,to;}e[N*2];
int head[N*2];
void init(){
    cnt=0;
    for(int i=0;i>u>>v;
    addedge(u,v);
    addedge(v,u);
}

4.Vector 直接存边

这个方法是最常用的一个。

直接用 vector e[N] 存边就可以了。

struct Edge{int to,w;};
vector e[N];
...

遍历

这里只介绍后两种常见方法的遍历方法,前两种请读者自行探讨。

链式前向星

for(int i=head[u];~i;i=e[i].nxt){
    ...
}

Vector 直接存边

for(auto it:e[u]){
    ...
}

那么,学会存图和遍历后,来做一下几道模板题来练一练吧:

二、拓扑排序

首先,你要了解拓扑在干什么:

给一个有向无环图(DAG)的所有节点排序————OI_WIKI

那么,拓扑排序之后,就一定有:

  • 每个顶点仅出现一次;
  • 对于有向边 A->B,在序列中都必有 AB前。

操作还是比较简单的:

  • 1.找到入度为 \(0\) 的点并输出;
  • 2.将所有与该点连接的边删除,邻居入度减 \(1\)
  • 3.重复步骤 1~2 ,直到没有任何点入度为 \(0\) 为止。

\({\color{red}注意:有环图是不能进行拓扑排序的!!}\)

学了拓扑,还是做一道模板题吧:
【模板】拓扑排序/家谱树

三、欧拉路

欧拉路其实就是一个简单的一笔画问题。

它的定义是:从图中某一个点出发,遍历整个图,图中每一条边通过且仅通过一次
欧拉回路就是起点和终点一样的欧拉路

一般关于欧拉路的问题基本上就是:

  • 是否有欧拉路。
  • 打印欧拉路。

通常是通过处理来解决问题的。

其中,度数为奇数的为奇点,度数为偶数的为偶点。

1.存在性判断

无向图

若全是偶点,存在欧拉回路,显然。
若有 \(2\) 个奇点,存在欧拉路,一个是起点,另一个是终点。

有向图

和无向图差不多,我们将一个点的度数记作入度减去出度。

若所有点度数为零,才会存在欧拉回路。
若仅有一个点度数为 \(1\),一个点度数为 \(-1\),其余点的度数为零,才存在欧拉路,\(1\) 的为起点,\(-1\) 的为终点。

2.输出欧拉回路

法1:DFS

法2:栈

习题

四、连通性问题

关于连通性:

  • 连通:对于无向图的两点 \(u,v\) ,若存在途径使得 \(v_0=u\)\(v_k=v\),则称 \(u,v\) 连通
  • 连通图:任意两点连通的无向图称为连通图

1.无向图

一些定义:

  • 割点:对于 \(G=(V,E)\)\(x \in V\),若删去 \(x\) 以及关联的边后,图分裂为两个及以上不相连的子图,则称 \(x\)\(G\)割点
  • 割边(桥):同上。

首先,我们要会求割点和割边:

很明显考虑 Tarjan\(^0\) 算法。

求割点 【模板】割点(割顶)

依赖一下两个主要的数组:

  • 追溯值 \(low_x\) :定义为以 \(x\) 为根的搜索树上的点以及通过一条不在搜索树上的边能达
    到的结点中的最小编号。
  • 时间戳 \(dfn_x\) :定义为 \(x\) 按访问顺序的编号。
//-------Tarjan部分
void Tarjan(int u){
    dfn[u]=low[u]=++Time;
    int son=0;
    for(auto v:e[u]){
        if(dfn[v]){
            low[u]=min(low[u],dfn[v]);
            continue;
        }
        son++;
        Tarjan(v);
        low[u]=min(low[u],low[v]);
        if(low[v]>=dfn[u]&&u!=root)cnt+=!is_cut[u],is_cut[u]=1;
    }
    if(son>=2&&u==root)cnt+=!is_cut[u],is_cut[u]=1;
}
//--------主函数部分
for(int i=1;i<=n;i++)
    if(!dfn[i])root=i,Tarjan(i);

求割边 【模板】割边(桥)

和割点很像:

void Tarjan(int u,int f){
    dfn[u]=low[u]=++cnt;
    for(int i=head[u];i;i=dis[i].nxt){
        int v(dis[i].to);
        if(v==f) continue;
        if(!dfn[v]){
            Tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u]){
            	e[++tot][0]=u;e[tot][1]=v;
            }
        }else low[u]=min(low[u],dfn[v]);
    }
}

习题:

割点和割边可以引出以下这些更为重要的定义:

  • 点双连通图:不存在割点的无向连通图称为点双连通图
  • 边双连通图:不存在割边的无向连通图称为边双连通图
  • 点双连通分量:一张图的极大点双连通子图称为点双连通分量(v-DCC\(^1\),简称点双
  • 边双连通分量:一张图的极大边双连通子图称为边双连通分量(e-DCC\(^2\),简称边双

但是完全看不懂。

说简单一点就是:

在一张连通的无向图中,对于两个点 \(u\)\(v\),如果无论删去哪一条边都不能使它们不连通,我们就说 \(u\)\(v\) 边双连通。——OI_WIKI

在一张连通的无向图中,对于两个点 \(u\)\(v\),如果无论删去哪一个点(不能删 \(u\)\(v\) 自己)都不能使它们不连通,我们就说 \(u\)\(v\) 点双连通。——OI_WIKI

\({\color{red}要注意的是,虽然割边不隶属于任何边双,但是割点是有可能隶属于多个点双的。}\)

求边双 【模板】边双连通分量

边双连通分量的求法很简单。并不比求割边的代码长太多,依旧依赖一下两个主要的数组:

  • 追溯值 \(low_x\)
  • 时间戳 \(dfn_x\)

在求割边的时候,我们已经对所有割边进行了标记。
所以我们只要再次 DFS ,不访问割边,对每一各连通块进行标记,则可以得到边双。

求点双 【模板】点双连通分量

点双联通分量的求法就没有边双那么简单了。虽然割边不隶属于任何边双,但是割点是有可能隶属于多个点双的。

所以此时我们就应该在跑 Tarjan 的过程中维护一个栈。

  • 当一个节点第一次被访问时,入栈。
  • dfn[x]<=low[y] 成立时,不断弹栈,直至 \(y\) 被弹出。而且刚刚弹掉的节点和 \(x\) 构成一个 v-DCC 。

习题:

边双:

点双:

2.有向图

一些定义:

  • 强连通:若一张有向图的节点两两互相可达,则称这张图是强连通的
  • 强连通分量:即极大的强连通子图,称为SCC\(^3\)

首先,我们要了解 DFS 生成树

我们在一个有向图上选一点 \(r\) ,从 \(r\) 开始遍历,每一个点只遍历一次,这样就会构成一棵树,即 DFS 生成树。

这棵树上会有这么几种边:

  • 树边:每次由⼀个已遍历的点到达未遍历的点,就形成了⼀条树边。
  • 返祖边:也叫做回边,指向该节点的祖先。
  • 横叉边:搜索时遇到⼀个已访问的节点,但这个节点并不是该节点的祖先。
  • 前向边:访问时遇到⼦树中的节点时形成的。

举个栗子,如图:

图中的红边即为返祖边,紫边为横插边,黄边为前向边,其余为树边。

求强连通分量有很多方法,由于作者很菜,本文只介绍 \(1\) 种。

Tarjan 算法

这个算法依旧是用栈来处理。

DFS 流程很简单:

  • 我们把当前节点 \(u\) 入栈。
  • \(dfn_u=low_u\) ,则开始弹栈,直到 \(u\) 被弹出去。
  • 弹出去的点构成一个 SCC。

习题

注释

  • \(^0\):Robert E. Tarjan(罗伯特·塔扬),生于美国加州波莫纳,计算机科学家.Tarjan 发明了很多算法和数据结构。比如求各种连通分量的 Tarjan 算法、求 LCA 的 Tarjan 、并查集、Splay、LCT 也是 Tarjan 发明的。
  • \(^1\)\(vertex \ double \ connected \ component\) 的缩写。
  • \(^2\)\(edge \ double \ connected \ component\) 的缩写。
  • \(^3\)\(strongly \ connected \ components\) 的缩写。

原文地址: https://www.cveoy.top/t/topic/qGNz 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录