图论

发布时间 2023-10-08 15:16:40作者: Biuld
图论这……感觉是知识点多,算法多,但用的频率不一定高,容易忘记,这里就整点板子防老年痴呆

欧拉路径

即一笔画问题,定义为:
图中经过所有边恰好一次的路径叫欧拉路径。如果此路径的起点和终点相同,则称其为一条欧拉回路

注意图必须连通,可用并查集或dfs判断

关于判断欧拉路径是否存在:

以下 \(S\) 为起点, \(T\) 为终点,并保证唯一

  • 有向图欧拉路径:\(S\) : 入度 + 1 = 出度; \(T\) : 入度 = 出度 + 1; 其余节点: 入度 = 出度
  • 有向图欧拉回路:所有节点: 入度 = 出度
  • 无向图欧拉路径:\(S, T\) : 度数为奇; 其余节点: 度数为偶
  • 无向图欧拉回路:所有节点: 度数为偶

在欧拉回路中,起点终点可以为任意点

关于求出欧拉回路

欧拉回路与欧拉路径(上)
欧拉回路与欧拉路径(下)
有向图:
\(S\) 开始跑dfs,每条边跑完后删去(打上标记),跑完所有儿子后再将该点入栈。
最后将整个栈反过来即为答案
关于正确性可画图感性理解

我是懒狗我不管

参考代码:

//N 为点的数量, M 为边的数量
int head[N], tot;
struct edge{
	int to, nxt;
	bool vis;
}e[M];
//有向边

int ans[M], cnt;
//每条边一次,共 M + 1 个点
//注意 M 开为数据范围 +5 即可

inline void Dfs(int now){
	for(int i = head[now]; i; i = e[i].nxt){
		if(!e[i].vis){
		//未访问过
			e[i].vis = 1;
			Dfs(e[i].to);
			//访问
		}
	}
	ans[++ cnt] = now;
	//入答案栈
}

一定注意最后答案是倒着入的栈,故需倒序输出

发现若用链式前向星存图,我们找未访问过的边时需将与该点相连的所有边都访问一次。
在这一操作上,其实使用领接链表更为便捷,以下为代码。

int num[N];//用法具体看Dfs函数
vector<int> to[N];
int ans[M], cnt;

inline void Dfs(int now){
	for(int i = num[now]; i < to[now].size(); i = num[now]){
	//以访问过的节点再也不会被访问啦
		num[now] ++;
		Dfs(to[now][i]);
	}
	ans[++ cnt] = now;
}

其实链式前向星应该也是支持这个操作的。只是

我是懒狗我不管

下面会提到如何实现

无向图:
类似于有向图的方法,只是删边时正边和反边一起删除罢了。

此时,若使用领接链表,会发现并不好处理删边,我们转回链式前向星。
实际上我们只是在找边时将查询的起始位置更新了,来避免多余的查询,将这思想放在链式前向星上,便是以下的代码。

int head[N], tot = 1;
//tot 从奇数开始方便查询反边
struct edge{
	int to, nxt;
	bool vis;
	//因为有未访问但被删除的反边存在,我们还是需要删除标记
}e[M << 1];
//无向图

int ans[M], cnt;

inline void Dfs(int now){
	for(int i = head[now]; i; i = head[now]){
		if(e[i].vis){
		//边已被删
			head[now] = e[i].nxt;
			continue;
		}
		e[i].vis = 1;
		e[i ^ 1].vis = 1;
		//删边
		head[now] = e[i].nxt;
		Dfs(e[i].to);
		
		ans[++ cnt] = now;
		//此句放循环外或内还并不清楚,有知道的麻烦留言提醒,谢谢了
	}
}