数据结构|并查集

本文探讨了利用图论解决实际问题的实例,包括城镇道路规划中如何通过最少建设达到全省连通,判断连通图的连通性,以及亲属关系的追溯。通过算法演示了如何使用并查集和深度优先搜索等技术来优化网络结构和查找直系关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

畅通工程|浙大考研复试

描述
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
输入描述:
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。 注意:两个城市之间可以有多条道路相通,也就是说 3 3 1 2 1 2 2 1 这种输入也是合法的 当N为0时,输入结束,该用例不被处理。
输出描述:
对每个测试用例,在1行里输出最少还需要建设的道路数目。

#include<iostream>
#include<cstdio>

using namespace std;

const int MAXN=1000;

int father[MAXN];//父亲结点
int height[MAXN];//结点高度

void Initial(int n){//初始化
    for(int i=0;i<=n;i++){
        father[i]=i;//每个结点的父亲为自己
        height[i]=0;//每个结点的高度为0
    }
}

int Find(int x){//查找根结点
    if(x!=father[x]){//路径压缩
        father[x]=Find(father[x]);
    }
    return father[x];
}

void Union(int x,int y){//合并并查集
    x=Find(x);
    y=Find(y);
    if(x!=y){//矮树作为高树的子树
        if(height[x]<height[y]){
            father[x]=y;
        }else if(height[y]<height[x]){
            father[y]=x;
        }else{
            father[y]=x;//y的父节点为x
            height[x]++;
        }
    }
    return;
}

int main(){
    int n,m;
    while(scanf("%d",&n)!=EOF){
        if(n==0)  break;
        scanf("%d",&m);
        Initial(n);//初始化
        while(m--){
            int x,y;
            scanf("%d%d",&x,&y);
            Union(x,y);//合并集合
        }
        int answer=-1;//思考如此设置初始值的好处
        for(int i=1;i<=n;i++){
            if(Find(i)==i){//集合数目
                answer++;
            }
        }
        printf("%d\n",answer);
    }
    return 0;
}

连通图|吉林大学考研复试

描述
给定一个无向图和其中的所有边,判断这个图是否所有顶点都是连通的。
输入描述:
每组数据的第一行是两个整数 n 和 m(0<=n<=1000)。n 表示图的顶点数目,m 表示图中边的数目。随后有 m 行数据,每行有两个值 x 和 y(0<x, y <=n),表示顶点 x 和 y 相连,顶点的编号从 1 开始计算。输入不保证这些边是否重复。
输出描述:
对于每组输入数据,如果所有顶点都是连通的,输出"YES",否则输出"NO"。

#include<iostream>
#include<cstdio>
using namespace std;

const int MAXN=1002;
int father[MAXN];
int height[MAXN];

void Init(int n){
    for(int i=0;i<=n;i++){
        father[i]=i;
        height[i]=0;
    }
}

int Find(int x){//查找根结点
    if(x!=father[x]){//压缩路径
        father[x]=Find(father[x]);
    }
    return father[x];
}

void Union(int x,int y){//合并集合
    x=Find(x);
    y=Find(y);
    if(x!=y){//如何相等则已经在一个集合中,不用合并
        if(height[x]<height[y]){
            father[x]=y;
        }
        else if(height[y]<height[x]){
            father[y]=x;
        }else{
            father[y]=x;
            height[x]++;
        }
    }
    return;
}

int main(){
    int n,m;
    while(scanf("%d",&n)!=EOF){
        if(n==0) break;
        scanf("%d",&m);
        Init(n);
        while(m--){
            int x,y;
            scanf("%d%d",&x,&y);
            Union(x,y);
        }
        int component=0;//连通分量
        for(int i=1;i<=n;i++){
            if(Find(i)==i){
                component++;
            }
        }
        if(component==1) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

Is it a tree?

描述
A tree is a well-known data structure that is either empty (null, void, nothing) or is a set of one or more nodes connected by directed edges between nodes satisfying the following properties. There is exactly one node, called the root, to which no directed edges point. Every node except the root has exactly one edge pointing to it. There is a unique sequence of directed edges from the root to each node. For example, consider the illustrations below, in which nodes are represented by circles and edges are represented by lines with arrowheads. The first two of these are trees, but the last is not.
输入描述:
The input will consist of a sequence of descriptions (test cases) followed by a pair of negative integers. Each test case will consist of a sequence of edge descriptions followed by a pair of zeroes Each edge description will consist of a pair of integers; the first integer identifies the node from which the edge begins, and the second integer identifies the node to which the edge is directed. Node numbers will always be greater than zero and less than 10000.
输出描述:
For each test case display the line “Case k is a tree.” or the line “Case k is not a tree.”, where k corresponds to the test case number (they are sequentially numbered starting with 1).
示例1
输入:
6 8 5 3 5 2 6 4
5 6 0 0

8 1 7 3 6 2 8 9 7 5
7 4 7 8 7 6 0 0

3 8 6 8 6 4
5 3 5 6 5 2 0 0
-1 -1
复制
输出:
Case 1 is a tree.
Case 2 is a tree.
Case 3 is not a tree.

#include<iostream>
#include<cstdio>
using namespace std;

const int MAXN=10000;

int father[MAXN];
int height[MAXN];
int inDegree[MAXN];//入度
bool visit[MAXN];//标记

void Init(){//初始化
    for(int i=0;i<MAXN;i++){
        father[i]=i;
        height[i]=0;
        inDegree[i]=0;//入度
        visit[i]=false;
    }
}

int Find(int x){//查找根结点
    if(x!=father[x]){
        father[x]=Find(father[x]);
    }
    return father[x];
}

void Union(int x,int y){
    x=Find(x);
    y=Find(y);
    if(x!=y){
        if(height[x]<height[y]){
            father[x]=y;
        }else if(height[y]<height[x]){
            father[y]=x;
        }else{
            father[y]=x;
            height[x]++;
        }
    }
    return;
}

bool isTree(){
    bool flag=true;
    int component=0;//连通分量数目
    int root=0;//根结点数目
    for(int i=0;i<MAXN;i++){
        if(!visit[i]) continue;//?
        if(father[i]==i) component++;
        if(inDegree[i]==0) root++;//树只有根结点满足入度为0
        else if(inDegree[i]>1) flag=false;
        if(component!=1||root!=1) flag=false;
        if(component==0&& root==0) flag=true;//空集也是树
    }
    return flag;
}

int main(){
    int x,y;
    int caseNumber=0;
    Init();
    while(scanf("%d%d",&x,&y)!=EOF){
        if(x==-1&&y==-1) break;
        if(x==0&&y==0){
            if(isTree()) printf("Case %d is a tree.\n",++caseNumber);
            else printf("Case %d is not a tree.\n",++caseNumber);
            Init();
        }else{
            Union(x, y);
            inDegree[y]++;
            visit[x]=true;
            visit[y]=true;
        }
    }
    return 0;
}

找出直系亲属|浙大考研复试

描述
如果A,B是C的父母亲,则A,B是C的parent,C是A,B的child,如果A,B是C的(外)祖父,祖母,则A,B是C的grandparent,C是A,B的grandchild,如果A,B是C的(外)曾祖父,曾祖母,则A,B是C的great-grandparent,C是A,B的great-grandchild,之后再多一辈,则在关系上加一个great-。
输入描述:
输入包含多组测试用例,每组用例首先包含2个整数n(0<=n<=26)和m(0<m<50), 分别表示有n个亲属关系和m个问题, 然后接下来是n行的形式如ABC的字符串,表示A的父母亲分别是B和C,如果A的父母亲信息不全,则用-代替,例如A-C,再然后是m行形式如FA的字符串,表示询问F和A的关系。
输出描述:
如果询问的2个人是直系亲属,请按题目描述输出2者的关系,如果没有直系关系,请输出-。 具体含义和输出格式参见样例.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
const int MAXN = 30;
int son[MAXN];    //记录谁是谁的儿子,son[a] = b代表a 的儿子是b,b按组下标访问
bool check(int child, int parent, string str) {    //查找parent是否有child这个后代
	int h = 1;
	while (son[parent] != -1) {    //用链表的方式访问
		if (child != son[parent]) {
			//cout << char(parent + 'A') << "'s son is " << char(son[parent] + 'A') << " but not " << char(child + 'A') << endl;
			h++;
			if (h == 2) str = "grand" + str;
			parent = son[parent];
		}
		else {
			while (h-- > 2)     //通过h计算相隔代数并加到str上
				str = "great-" + str;
			cout << str << endl;
			return true;    //若存在后代为child,则立刻输出并返回true
		}
	}
	return false;
}
int main() {
	int n, m, x, y;
	string str;
	while (scanf("%d %d\n", &n, &m) != EOF) {
		memset(son, -1, sizeof(son));
		for (int i = 0; i < n; i++) {
			cin >> str;
			x = str[0] - 'A';
			if (str[1] != '-') 	son[str[1] - 'A'] = x;
			if (str[2] != '-') 	son[str[2] - 'A'] = x;
		}
		while (m--) {
			cin >> str;
			x = str[0] - 'A', y = str[1] - 'A';
			str = "child";
			if (check(x, y, str)) continue;    //双向遍历
			str = "parent";
			if (check(y, x, str)) continue;
			printf("-\n");
		}
	}
	return 0;
}

Head Of a Gang|PAT

描述
One way that the police finds the head of a gang is to check people’s phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be the total time length of all the phone calls made between the two persons. A “Gang” is a cluster of more than 2 persons who are related to each other with total relation weight being greater than a given threthold K. In each gang, the one with maximum total weight is the head. Now given a list of phone calls, you are supposed to find the gangs and the heads.
输入描述:
For each case, the first line contains two positive numbers N and K (both less than or equal to 1000), the number of phone calls and the weight threthold, respectively. Then N lines follow, each in the following format: Name1 Name2 Time where Name1 and Name2 are the names of people at the two ends of the call, and Time is the length of the call. A name is a string of three capital letters chosen from A-Z. A time length is a positive integer which is no more than 1000 minutes.
输出描述:
For each test case, first print in a line the total number of gangs. Then for each gang, print in a line the name of the head and the total number of the members. It is guaranteed that the head is unique for each gang. The output must be sorted according to the alphabetical order of the names of the heads.

#include<iostream>
#include<map>
#include<string>
using namespace std;
//头是要点权最高的来。
//边权怎么记录?
const int maxn=1001;
int father[maxn];
int height[maxn]; //点权
int weight[maxn]; //规定顺序假如输入的是 AAA BBB 10,那么A的边权+10,B的边权不加。这样防止重复加边权,而且可以直接通过遍历得到边权。 
struct r{           //记录结果result 
    int people,value;
};
map<string,r> res;//用于输出时候的顺序
map<string,int> finalres; 
map<string,int> stringToInt;//形成字符串转int的映射 
map<int,string> intToString;//形成int转字符串的映射
int index;//映射函数对应的下标。也是所有的人数 
void init(){
    res.clear();
    stringToInt.clear();
    intToString.clear();
    finalres.clear();
    for(int i=0;i<maxn;i++){
        father[i]=i;
        height[i]=0;
        weight[i]=0;
    }
    index=0;
}
int change(string str){//映射函数 
    if(stringToInt.find(str)!=stringToInt.end())
        return stringToInt[str];
    stringToInt[str]=index;//形成双向映射 
    intToString[index]=str;
    return index++; 
} 
int find(int x){
    if(father[x]!=x)
        father[x]=find(father[x]);
    return father[x]; 
} 
void Union(int x,int y){ //weight点权最大的当头节点,可能会有子节点的点权大于父节点的情况 
    int headx,heady;
    int fx=find(x);
    int fy=find(y);
    headx=fx;heady=fy;
    if(height[x]>height[fx]){ //判断子节点和父节点的情况,始终让 父节点的点权最大 
        father[fx]=x;
        father[x]=x;
        headx=x;
    }
    if(height[y]>height[fy]){
        father[fy]=y;
        father[y]=y;
        heady=y;
    }
    if(height[headx]>=height[heady])
        father[heady]=headx;
    else if(height[headx]<height[heady])
        father[headx]=heady;
}
int main()
{
    int n,k;
    while(cin>>n>>k){
        init();
        int x,y,w;string s1,s2;
        for(int i=0;i<n;i++){
            cin>>s1>>s2>>w;
            x=change(s1);
            y=change(s2);
            weight[x]+=w;
            height[x]+=w;
            height[y]+=w;
            Union(x,y);
        }
        for(int i=0;i<index;i++){
            find(i);//所有路径都压缩一次,最后变成只有一个根的树 
            res[intToString[father[i]]].people++;
            res[intToString[father[i]]].value+=weight[i];
        }int sum=0;
        for(auto it=res.begin();it!=res.end();it++){
            if(it->second.people>2&&it->second.value>k)    {
                finalres[it->first]=it->second.people;
                sum++;
            }    
        }
        cout<<sum<<endl;
        for(auto it=finalres.begin();it!=finalres.end();it++){
            cout<<it->first<<" "<<it->second<<endl;
        }    
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值