108. 冗余连接
卡码网题目链接(ACM模式)(opens new window)
题目描述
树可以看成是一个图(拥有 n 个节点和 n - 1 条边的连通无环无向图)。
现给定一个拥有 n 个节点(节点编号从 1 到 n)和 n 条边的连通无向图,请找出一条可以删除的边,删除后图可以变成一棵树。
输入描述
第一行包含一个整数 N,表示图的节点个数和边的个数。
后续 N 行,每行包含两个整数 s 和 t,表示图中 s 和 t 之间有一条边。
输出描述
输出一条可以删除的边。如果有多个答案,请删除标准输入中最后出现的那条边。
分析:
主要目的就是要把环去掉!防止有环出现
怎么去掉环:已经有边连接的结点,通过并查集放到一起
如果已经在一起的结点之间 有又一条边——那就会构成环
题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树(即:只有一个根节点)。
如果有多个答案,则返回二维数组中最后出现的边。
那么我们就可以从前向后遍历每一条边(因为优先让前面的边连上),边的两个节点如果不在同一个集合,就加入集合(即:同一个根节点)。
如图所示:
节点A 和节点 B 不在同一个集合,那么就可以将两个 节点连在一起。
如果边的两个节点已经出现在同一个集合里,说明着边的两个节点已经连在一起了,再加入这条边一定就出现环了。
如图所示:
已经判断 节点A 和 节点B 在在同一个集合(同一个根),如果将 节点A 和 节点B 连在一起就一定会出现环。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
int find(int*father,int x){
if(father[x]==x) return x;
else{
father[x]=find(father,father[x]);
return father[x];
}
}
void join(int*father,int s,int t){
int x=find(father,s);
int y=find(father,t);
if(x==y) return;
else father[x]=y;
}
bool issame(int*father,int s,int t){
int x=find(father,s);
int y=find(father,t);
if(x==y) return true;
return false;
}
int main(){
int n;
scanf("%d",&n);
int* father=(int*)malloc(sizeof(int)*(n+1));
for(int i=1;i<=n;i++) father[i]=i;
int s,t;
for(int i=0;i<n;i++){
int s,t;
scanf("%d%d",&s,&t);
if(issame(father,s,t)){
printf("%d %d",s,t);
return 0;
}
else join(father,s,t);
}
printf("%d%d",s,t);
return 0;
}
109. 冗余连接II
卡码网题目链接(ACM模式)(opens ne
w window)
题目描述
有向树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。有向树拥有 n 个节点和 n - 1 条边。
输入一个有向图,该图由一个有着 n 个节点(节点编号 从 1 到 n),n 条边,请返回一条可以删除的边,使得删除该条边之后该有向图可以被当作一颗有向树。
输入描述
第一行输入一个整数 N,表示有向图中节点和边的个数。
后续 N 行,每行输入两个整数 s 和 t,代表 s 节点有一条连接 t 节点的单向边
输出描述
输出一条可以删除的边,若有多条边可以删除,请输出标准输入中最后出现的一条边。
分析:
本题的本质是 :有一个有向图,是由一颗有向树 + 一条有向边组成的 (所以此时这个图就不能称之为有向树),现在让我们找到那条边 把这条边删了,让这个图恢复为有向树。
还有“若有多条边可以删除,请输出标准输入中最后出现的一条边”,这说明在两条边都可以删除的情况下,要删顺序靠后的边!
我们来想一下 有向树的性质,如果是有向树的话,只有根节点入度为0,其他节点入度都为1(因为该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点)。
所以情况一:如果我们找到入度为2的点,那么删一条指向该节点的边就行了。
如图:
找到了节点3 的入度为2,删 1 -> 3 或者 2 -> 3 。选择删顺序靠后便可。
但 入度为2 还有一种情况,情况二,只能删特定的一条边,如图:
节点3 的入度为 2,但在删除边的时候,只能删 这条边(节点1 -> 节点3),如果删这条边(节点4 -> 节点3),那么删后本图也不是有向树了(因为找不到根节点)。
综上,如果发现入度为2的节点,我们需要判断 删除哪一条边,删除后本图能成为有向树。如果是删哪个都可以,优先删顺序靠后的边。
情况三: 如果没有入度为2的点,说明 图中有环了(注意是有向环)。
如图:
对于情况三,删掉构成环的边就可以了。
**创造数组的时候要注意下标 和 真实含义的对应关系!!
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
int count[1000];
void init(int*father,int n){
for(int i=1;i<=n;i++) father[i]=i;
}
int find(int*father,int x){
if(father[x]==x) return x;
else{
father[x]=find(father,father[x]);
return father[x];
}
}
void join(int*father,int s,int t){
int x=find(father,s);
int y=find(father,t);
if(x==y) return;
else father[x]=y;
}
bool issame(int*father,int s,int t){
int x=find(father,s);
int y=find(father,t);
if(x==y) return true;
return false;
}
int validtree(int*father,int **record,int n){//return要去掉第几条边
for(int i=1;i<=n;i++) father[i]=i;
for(int i=0;i<n;i++){
int x = record[i][0];
int y = record[i][1];
if(issame(father,x,y)) return i;
else join(father,x,y);
}
return -1;
}
bool validtree2(int*father,int**record,int n,int skip){
for(int i=1;i<=n;i++) father[i]=i;
for(int i=0;i<n;i++){
if(i==skip) continue;
int x= record[i][0];
int y=record[i][1];
if(issame(father,x,y)) return false;
else join(father,x,y);
}
return true;
}
int main(){
int n;
scanf("%d",&n);
memset(count,0,sizeof(count));
int* father=(int*)malloc(sizeof(int)*(n+1));
for(int i=0;i<=n;i++) father[i]=i;
int **record =(int**)malloc(sizeof(int*)*n);
for(int i=0;i<n;i++) record[i]=(int*)malloc(sizeof(int)*2);
int s,t;
int max=1,index=0;
for(int i=0;i<n;i++){
int s,t;
scanf("%d%d",&s,&t);//输入
record[i][0]=s;//按照顺序记录到record中
record[i][1]=t;
count[t]=count[t]+1;//记录入度
if (count[t]==2){//最多有一个为2
max=2;
index=t;
}
}
//printf("index=%d\n",index);
if(max==2){//index的入度是2
//优先删后面的那条边
int first,second;
for(int i=n-1;i>=0;i--){
if(record[i][1]==index){
first=i;
break;
}
}
for(int i=first-1;i>=0;i--){
if(record[i][1]==index){
second=i;
break;
}
}
//printf("1:%d 2:%d\n",first,second);
if(validtree2(father,record,n,first)) printf("%d %d",record[first][0],record[first][1]);
else printf("%d %d",record[second][0],record[second][1]);
}
else{//入度是1,也就是出现有向环
int ans=validtree(father,record,n);
printf("%d %d",record[ans][0],record[ans][1]);
}
return 0;
}