图的专题(持续更新)

一、并查集

eg1. 《算法笔记》P332——好朋友
在这里插入图片描述

思路分析:

利用并查集,最终分成的组数就是集合的个数,也就是最后根节点的个数。

代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn = 105;
int father[maxn];
bool isRoot[maxn]; // 判断结点是否为根节点 

void init(int n) // 初始化,每个元素的父亲结点设为自己 
{
	for(int i=1; i<=n; i++)
	{
		father[i] = i;
		isRoot[i] = true;
	}
}

/*
int findFather(int x) // 无路径压缩查找 
{
	while(x != father[x])
	{
		x = father[x];
	}
	return x;
}
*/

int findFather(int x) // 路径压缩查找
{
	int a = x;
	while(x != father[x])
	{
		x = father[x];
	}
	while(a != father[a]) // 所查找的元素到根节点之间,所经过的元素的父亲结点都设为根节点 
	{
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
} 


void Union(int a, int b) // 合并
{
	int father_a = findFather(a);
	int fatehr_b = findFather(b);
	if(father_a != fatehr_b)
	{
		father[father_a] = fatehr_b;
		isRoot[father_a] = false; // 合并后将其中一个根节点取消掉 
	}
} 

int main()
{
	freopen("input.txt", "r", stdin);
	int n, m;
	scanf("%d %d", &n, &m);
	init(n);
	for(int i=1; i<=m; i++)
	{
		int temp1, temp2;
		scanf("%d %d", &temp1, &temp2);
		Union(temp1, temp2);	
	}
	int ans = 0;
	for(int i=1; i<=n; i++)
	{
		ans += isRoot[i];
	}
	printf("%d", ans);
	
	fclose(stdin);
	return 0;	
} 

eg2. 《王道》P106——畅通工程
在这里插入图片描述

思路分析:

利用并查集,找出分成的组个数,需要新建的路个数为组数减一。

代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn = 1010;
int father[maxn];
bool isRoot[maxn];
int ans = 0;

void init(int n)
{
	ans = 0;
	for(int i=1; i<=n; i++)
	{
		father[i] = i;
		isRoot[i] = true;
	}
}

int findFather(int x)
{
	int a = x;
	while(x != father[x])
	{
		x = father[x];
	}
	while(a != father[a])
	{
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
}

void Union(int a, int b)
{
	int father_a = findFather(a);
	int father_b = findFather(b);
	if(father_a != father_b)
	{
		father[father_b] = father_a;
		isRoot[father_b] = false;
	}
}

int main()
{
	freopen("input.txt", "r", stdin);
	int n, m;
	while(scanf("%d %d", &n, &m)!=EOF && n!=0)
	{
		init(n); // 初始化 
		int temp1, temp2;
		for(int i=0; i<m; i++)
		{
			scanf("%d %d", &temp1, &temp2);
			Union(temp1, temp2);
		}
		for(int i=1; i<=n; i++)
		{
			ans += isRoot[i];
		}
		
		printf("%d\n", ans-1);
	}
	
	fclose(stdin);
	return 0;
}

eg3. PAT A1107 Social Clusters
在这里插入图片描述
在这里插入图片描述

思路分析:

由于是根据兴趣进行分类、并查集操作,所以可以先将所有人存入其对应的兴趣列表中,再依次对每个兴趣列表进行并查集操作。

代码(AC)
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn = 1010;
int father[maxn];
int isRoot[maxn];
vector<int> hobby[maxn];

void init(int n)
{
	for(int i=1; i<=n; i++)
	{
		father[i] = i;
		isRoot[i] = 1;
	}
}

int findFather(int x)
{
	int a = x;
	while(x != father[x])
	{
		x = father[x];
	}
	while(a != father[a])
	{
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
}

void Union(vector<int> v)
{
	int father_a = findFather(v[0]);
	for(int i=1; i<v.size(); i++)
	{
		int father_b = findFather(v[i]);
		if(father_a != father_b)
		{
			father[father_b] = father_a;
			isRoot[father_a] += isRoot[father_b];
			isRoot[father_b] = 0;
		}
	}
}

bool cmp(int a, int b)
{
	return a > b;
}

int main()
{
	freopen("input.txt", "r", stdin); 
	int n;
	scanf("%d", &n);
	init(n);
	for(int i=1; i<=n; i++)
	{
		int num, temp;
		scanf("%d: ", &num);
		for(int j=0; j<num; j++)
		{
			scanf("%d", &temp);
			hobby[temp].push_back(i);
		}
	}
	for(int i=1; i<=1000; i++)
	{
		if(hobby[i].size() > 1)
		{
			Union(hobby[i]);
		}
	}

	int ans = 0;
	for(int i=1; i<=n; i++)
	{
		if(isRoot[i] > 0)
		{
			ans++;
		}
	}
	printf("%d\n", ans);
	sort(isRoot+1, isRoot+n+1, cmp);
	for(int i=1; i<=ans; i++)
	{
		printf("%d", isRoot[i]);
		if(i < ans)
			printf(" ");
	}
	
	fclose(stdin);
	return 0;
}

eg4. PAT A1021 Deepest Root
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题目大意:

给出N个结点与N-1条边,问是否能形成一棵树。若能从中选取根节点使树的深度最大。

思路分析:

利用并查集,若最后只有一个集合,说明可以形成树,否则不能。对于形成的树,对树进行DFS。

先任意选择一个结点,从该结点开始遍历,获取能到达的最深顶点(集合A);
然后从集合A中任一个结点开始遍历,获取能到达的最深顶点(集合B);
集合A与集合B的并集,即为所求树高最大的根节点。

注:因为存储的是无向图,所以在进行DFS过程中,要将已经走过的路径跳过去,防止出现环路。

代码(未AC)
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn = 100010;
vector<int> G[maxn]; 
int father[maxn];
bool isRoot[maxn];

void init(int n)
{
	for(int i=1; i<=n; i++)
	{
		father[i] = i;
		isRoot[i] = true;
	}
}

int findFather(int x)
{
	int a = x;
	while(x != father[x])
	{
		x = father[x];
	}
	while(a != father[a])
	{
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
}

void Union(int a, int b)
{
	int father_a = father[a];
	int father_b = father[b];
	if(father_a != father_b)
	{
		father[father_b] = father_a;
		isRoot[father_b] = false;
	}
}

int maxH = 0; // 树的最大高度
vector<int> temp, ANS;

void DFS(int u, int height, int pre) // u为当前访问结点编号,height为当前树高(初值设1),pre为u的父节点(初值设-1) 
{
	if(height > maxH)
	{
		temp.clear();
		temp.push_back(u);
		maxH = height;	
	} 
	else if(height == maxH) // temp中存放遍历到的深度最大结点 
	{
		temp.push_back(u);
	}
	for(int i=0; i<G[u].size(); i++)
	{
		if(G[u][i] == pre) // 因为是无向图,所以在树的遍历过程中,要将已经走过的路径跳过去,否则会形成环路 
		{
			continue;	
		}	
		DFS(G[u][i], height+1, u);
	}	
} 

int main()
{
	freopen("input.txt", "r", stdin);
	int n;
	scanf("%d", &n);
	init(n);
	for(int i=0; i<n-1; i++)
	{
		int temp1, temp2;
		scanf("%d %d", &temp1, &temp2);
		G[temp1].push_back(temp2);
		G[temp2].push_back(temp1);
	}
	for(int i=1; i<=n; i++)
	{
		int a = i;
		for(int j=0; j<G[i].size(); j++)
		{
			int b = G[i][j];
			Union(a, b);
		}
	}
	int ans = 0;
	for(int i=1; i<=n; i++)
	{
		ans += isRoot[i];
	}
	if(ans != 1)
	{
		printf("Error: %d components\n", ans);
	}
	else 
	{
		DFS(1, 1, -1); // 从任一个结点开始遍历 
		ANS = temp;
		maxH = 0;
		temp.clear();
		DFS(ANS[0], 1, -1); // 从任一个最深结点开始遍历
		for(int i=0; i<temp.size(); i++)
		{
			ANS.push_back(temp[i]);	// 将两组序列合并起来 
		} 
		sort(ANS.begin(), ANS.end());
		printf("%d\n", ANS[0]);
		for(int i=1; i<ANS.size(); i++) // 重复的不输出 
		{
			if(ANS[i] != ANS[i-1])
			{
				printf("%d\n", ANS[i]);
			}
		}
	}
	
	fclose(stdin);
	return 0;
}

二、图的遍历

eg1. PAT A1013 Battle Over Cities
在这里插入图片描述

题目大意:

给定一个无向图,当删除某个结点时,将会同时把与之连接的边一起删除。求解删除不同结点后,至少需要增加多少条边才能使图变为连通(所有操作均在原图上进行)。

思路分析:

本题其实就是求解连通块的个数,因而可以使用并查集,也可以使用图的DFS。本例中使用DFS方法。

注:删除结点,其实不需要真正的在数据结构中删除,而只需要在DFS过程中,遇到该已删除结点,直接return即可。

代码(AC)
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn = 1010;
vector<int> G[maxn]; // 邻接表 
bool vis[maxn] = {false}; // 记录结点是否被访问过 
int ans = 0;

void DFS(int v, int del) // DFS遍历顶点v所在的连通块,忽略顶点del 
{
	if(v == del)
		return ; //当遍历到删除结点del时,直接返回,可以起到结点删除的作用,而不需要在数据结构中真正删除 
	vis[v] = true; // 标记为已访问过
	for(int i=0; i<G[v].size(); i++)
	{
		if(vis[G[v][i]] == false)
		{
			DFS(G[v][i], del);
		}
	} 
} 

void DFSTrave(int n, int del) // 遍历图的所有结点 
{
	for(int u=1; u<=n; u++)
	{
		if(u != del && vis[u] == false)
		{
			ans++; // 连通块的个数 
			DFS(u, del);
		}
	}
}

int main()
{
	freopen("input.txt", "r", stdin);
	int n, m, k;
	scanf("%d %d %d", &n, &m, &k);
	for(int i=0; i<m; i++)
	{
		int temp1, temp2;
		scanf("%d %d", &temp1, &temp2);
		G[temp1].push_back(temp2);
		G[temp2].push_back(temp1); // 邻接表存储无向图 
	}
	for(int i=0; i<k; i++)
	{
		ans = 0;
		fill(vis, vis+maxn, false);
		int del;
		scanf("%d", &del);
		DFSTrave(n, del); // 遍历图中所有结点,跳过已删除结点	
		printf("%d\n", ans-1);
	}
	
	fclose(stdin);
	return 0;
} 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值