【问题描述】
树是一种大家都不陌生的数据结构,它有可能是一颗空树或是一些满足要求的节点连接而成的有向边的集合。
一棵树只有一个根节点,根节点没有指向它的边。
除了根节点的每一个节点都只有一条边指向它。
出现环的图都不是树。
对一些节点连接而成的有向边的集合进行判定,判定每一组的输入数据构成的图是否是一棵树。
【输入】
每输入一对都为0的数时,表示一组数据输入完毕。每条边由一对正整数表示,第一个数为有向边的起始点,第二个数为有向边的终止点。一对负数的输入表示输入的结束。
【输出】
每组测试数据输出一行判定结果,若输入的图为树,则输出“Case k is a tree.”,否则输出“Case k is not a tree.”。其中k表示每组数据的编号(编号从1开始)。
【解题报告】
运用并查集可以判定一个图是否为树。
根据树的定义与特点,需考虑的情况有:
(1)树中节点至多只能有一个父节点;
(2)树中不能出现环;
(3)构成的图只能有一个根节点,否则构成的将是森林而不是一棵树。
通过观察输入输出可知,题中编号是随即的,所以在编程中要对出现的点标记才能判断。
Step1:对每对输入的根节点标记表示这些节点出现过,进行并操作。并操作时两个节点不能有相同的根节点否则将构成环;假设b节点要接到a上,则要保证b节点是一个根节点,否则若进行并操作,b将会有两个父节点;若无以上情况,则可以合并两棵树。
Step 2:每组数据输入结束后,要计算整个图中的根节点总数,若根节点总数不为1,则构成的图不是一棵树。
Step 3:根据以上判断就可以输出结果了,每组结果输出后要初始化数据。
#include <stdio.h> #include <stdlib.h> #include <string.h> int father[1000000], flag[1000000]; int findset(int x)//查 { if(x != father[x]) father[x]=findset(father[x]);//路径压缩 return father[x]; } int unionset(int a, int b)//并 { int x=findset(a); int y=findset(b); if(y != b)//b连接在a上,要保证b是个根节点,否则b将有两个父节点 return 1; if(x == y)//如果a,b在同一棵树中,若再进行并操作就会产生环 return 1; else father[y]=x;//若无以上情况,则可以合并两棵树 return 0; } int main() { int a, b, key=0, p=1, i, max=0, t=0; for(i=1; i<=999999; i++) father[i]=i; memset(flag, 0, sizeof(flag)); while(1) { scanf("%d%d", &a, &b); if(a>max) max=a; if(b>max) max=b; if(a<=-1 && b<=-1) break; if(a==0 && b==0) { for(i=1; i<=max; i++) { if(flag[i]==1 && father[i]==i) t++; if(t>=2) break; } if(key>0 || t>=2) printf("Case %d is not a tree. ", p++); else printf("Case %d is a tree. ", p++); for(i=1; i<=999999; i++) father[i]=i; key=0; t=0; max=0; memset(flag, 0, sizeof(flag)); continue; } flag[a]=1;flag[b]=1;//对输入的编号标记 key+=unionset(a,b);//若出现不合法的情况,key的值会大于0 } }
