代码随想录算法训练营day56:图06:冗余连接(图变树)

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;
}

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配,帮助学员更好地理解和掌握二叉的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值