图论笔记

图论基础

图的基本概念

  • 图: 一个图由点集 V 和边集 E 组成, 如图 G = <V, E>
  • 有无向图: 只有有/无向边组成的图
  • 自环: 边连接的两个点事同一个点
  • 重边: 无向图中两点间有多边,有向图在两个点中有多条同方向的边
  • 孤点: 没有连接边的顶点
  • 简单图: 无自环和重边的图
  • 度数: 对于无向图,顶点 v 作为边端点的次数为 v 的度数, 记为 d(v), 对于有向图, 作为起点的个数为入度, 作为边终点的个数为出度
  • 最大度: 所有顶点度数最大值, 反之有最小度
    一个图 G 的所有点的度数和为边数的两倍, 有向图出度和等于入度和
  • 完全图(无向图): G 为 n 个节点的无向图, 每个顶点与其他 n-1 个点都有边相连, 则 G 为 n 阶无向完全图, n 阶完全图, 记作 \(K_n\)
  • 完全图(有向图): 相比无向完全图, 一条边有两个方向
  • 竞赛图: 基于 n 阶无向完全图, 给每条边任意确定一个方向形成的图是 n 阶竞赛图
  • 子图: G' 是 G 的子图, 当边集或点集为 G 的子集, 则 G 为 G' 的母图
  • 生成子图: 点集相同的子图
  • 真子图: 点集和边集都是母图的真子集
  • 补图: 图和它的补图顶点集相同; 边集的交集为空, 并集是完全图的边集
  • 同构: 对两个图 G 和 G' 存在双射 v -> v' ,使得对边 v -> v', 有 h(v) -> h(v') , 称两个图同构
  • 通路: 图中的一条道路, 边的条数为长度, 有向图中边的方向一致, 点的数目是边的数目 + 1
  • 回路: 通路的起点与终点相同, 中间的点都不同.
  • 迹: 通路中所有边都不同, 若所有顶点也不相同则称为路径
  • 距离: 图中连接两点的最短路径长度称为距离
  • 无向图的连通
    • 连通性: 图中 u 和 v 存在通路, 则 u, v 是连通的, 对于 v 和 v 子集也是连通的
    • 连通图: 图中任意两点都是连通的
    • 连通块: 对于图的一个连通子图 H, 不存在 H 的母图是连通图, 则 H 是 G 的一个连通块/连通分量, H 是一个极大连通子图.
  • 有向图的连通
    • 连通性: 图中存在 u -> v 的通路, 则 u 可达 v, 如果 u, v 互相可达, 则 u, v 连通
    • 强连通: 如果有向图 G 中的顶点两两可达, 则 G 为 强连通图
    • 强连通块: 类似于无向图中的连通块概念, 有强连通块/强连通分量

拓扑排序

  • 时间复杂度: \(O(n+m)\)
  • 拓扑排序是对有向无环图(DAG)的顶点进行一种线性排序, 序列中每个顶点只会出现一次, 对于所有有向边 u -> v,
    排序完后 u 都在 v 前面
  • 图中存在环, 就不能拓扑排序, 一个有向无环图可能存在多种拓扑排序结果
  • 最小拓扑序列, 最大拓扑序 使用优先队列实现

拓扑排序判断环

int n, m;
const int N = 1e4 + 10;
int d[N];
vector<int> edge[N];

bool topo(){
	queue<int> q;
	for(int i = 1; i <= n; i++){
		if(!d[i])
			q.push(i);
	}
	int cnt = 0;
	while(q.size()){
		cnt++;
		int t = q.front();
		q.pop();
		for(auto v: edge[t]){
			d[v] --;
			if(!d[v])
				q.push(v);
		}
	}
	return cnt == n;
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n >> m;
	while(m--){
		int a, b;
		cin >> a >> b;
		edge[a].pb(b);
		d[b] ++;
	}
	bool fl = topo();
	!fl ? cout << "Yes\n" : cout << "No\n";

    return 0;
}

欧拉路与欧拉回路

  • 欧拉路: 图中所有边恰好经过一次的通路称为欧拉通路或者欧拉路
  • 欧拉回路: 经过图中所有边恰好一次的回路称为欧拉回路
  • 欧拉回路判断:
    • 对于无向图 G, G 中所有度非 0 的点是连通的并且没有奇数度数的点
    • 对于有向图 G, G 中所有度非 0 的店是强连通的并且入度和出度相同
  • 欧拉路判断:
    • 对于无向图 G, G 中所有度非 0 的点是连通的并且奇数度数的点只有 0 或者 2 个
    • 对于有向图 G, G 中所有度非0的店是连通的(转为无向图), 且最多一个点出度=入度+1,入度=出度+1,其他点入度=出度

Code

有向图欧拉路

vector<int> edge[N];
int n, m, f[N], path[M], in[N], out[N], tot;
// f[i] 记录 i 点有多少条边
void dfs(int u){
	int len = edge[u].size();
	int x = 0;
	while(f[u] < len){
		int v = edge[u][f[u]];
		++f[u];
		dfs(v);
		path[++tot] = v;
	}
}

void Euler(){
	int x = 0, y = 0, z = 0;
	for(int i = 1; i <= n; i++){
		if(out[i] == in[i] + 1)
			y ++, x = i;
		if(out[i] != in[i])
			z++;
	}
	if(!(!z || (y == 1 && z == 2))){	// 不满足有向图欧拉路的条件 
		cout << "No\n";
		return;
	}
	if(!x)
		for(int i = 1; i <= n; i++)
			if(in[i]){
				x = i;
				break;
			}
	dfs(x);
	path[++tot] = x;
	if(tot == m + 1)	// 有向边变无向边是否连通可以用遍历过的边数是否为 m + 1 来检验
		cout << "Yes\n";
	else
		cout << "No\n";
}

无向图欧拉路

int n, m;
int d[N], cnt = 1, f[N], tot, path[N << 1];
bool st[N << 1];
vector<PII> edge[N];		// 终点、边的编号

void dfs(int u){
	int sz = edge[u].size();
	while(f[u] < sz){
		auto t = edge[u][f[u]];
		if(!st[t.y]){
			st[t.y] = st[t.y ^ 1] = true;
			f[u] ++;
			dfs(t.x);
			path[++tot] = t.x;
		}
		else
			f[u]++;
	}
}

void Euler(){
	int x = 0, y = 0;
	for(int i = 1; i <= n; i++)
		if(d[i] & 1)
			x = i, y++;
	if(y && y != 2){
		cout << "No\n";
		return;
	}
	if(!x)
		for(int i = 1; i <= n; i++)
			if(d[i])
				x = i;
	dfs(x);
	path[++tot] = x;
	tot == m + 1 ? cout << "Yes\n" : cout << "No\n"; 
}

二分图与最大匹配

拆点: 将 \(n\) 个点拆为 \(n\) 个进来的点和 \(n\) 出来的点.

int op[N * N];

bool find(int u){
	st[u] = true;
	for(auto v: edge[u]){
		if(!op[v] || (!st[op[v]] && find(op[v]))){
			op[v] = u;
			return true;
		}
	}
	return false;
}

int match(){
	int res = 0;
	for(int i = 1; i <= n1; i++){
		memset(st, false, sizeof st);
		if(find(i))
			res++;
	}
	return res;
}

习题记录

posted @ 2022-09-08 11:55  Roshin  阅读(117)  评论(0编辑  收藏  举报
-->