畅通工程 并查集(有环图时出错的改进)+(几次错误的更正)

本文探讨了并查集算法在解决城镇交通连通问题中的应用,详细分析了算法初始实现的问题,尤其是处理图中环的情况。通过对比不同并查集实现方式,包括路径压缩和按秩合并等优化策略,最终解决了问题并提高了算法效率。

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

首先想到的是最标准的并查集

但是WA了,因为图有环,导致一个环内有2个father节点(只被他人指向而不指向他人,导致的结果)

···思索到最后····

最后看来其实这也对了,依然是union函数错了(注释掉的部分),改掉之后AC了

fat[x]=find(fat[y]);

union中注释掉的部分这样的写法就会导致fat没有连接到最高

比如1-5,3-5,1-9,3-9

这样会导致fat[1]=fat[3]=9,fat[5]=5,fat[9]=9,

改进后,就会在1-9时,fat[find(1)]=fat[5]=9,这样就能连接起来了

fat[find(x)]=find(y);

 

#include<bits/stdc++.h>
using namespace std;
 /*
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。
省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通
(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。
问最少还需要建设多少条道路?

 */
vector<int>fat(1001,0);
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        fat[i]=i;
    }
}

int find(int x)
{
    if(x==fat[x]){return fat[x];}
    return find(fat[x]);
}

void uni(int x, int y)
{
    //fat[x]=find(fat[y]);
    fat[find(x)]=find(y);
}

int main(){
    //要用并查集
    int n,m;
    while(cin>>n>>m)
    {   int x,y;
         
         set<int>s;
         init(n);
         vector<int>v(n+1,0);
         for(int i=0;i<m;i++)
         {   cin>>x>>y;
              if(x==0||y==0){break;}
             uni(x,y);//并查集
              uni(y,x);
             v[x]=1;v[y]=1;//将出现过的节点保存
         }
         //出现过的节点的父亲放入set,最后看set的size即可知道有几个连通图,还差几个
         //比如4个节点,有2个连通图,且包含了3个节点
         //那么把两个连通图连起来一条边,把第四个节点加入再一条边
         int num=count(v.begin(),v.end(),1);
         for(int i=1;i<=n;i++)
         {
             if(v[i]==1)//访问过,就判断属于哪个连通子图
             {
                 s.insert(find(i));
             }
         }
         int num_lt=s.size();
         auto it=s.begin();
         cout<<fat[5]<<" "<<fat[6]<<fat[9]<<" "<<endl;
         cout<<n<<" "<<num<<" "<<num_lt<<endl;
         cout<<n-num+num_lt-1<<endl;
        
    }
    
    return 0;
}

改进:在father数组中,加上一个height(太麻烦了)

最后发现,其实只要去掉多此一举的init(之前看的并查集都是先把自己立为自己的father),就不会因为环导致一直指向自己

但是经过测试,这依然不对,因为这会导致所有人的fat都是0

(如输入uni(1,2),因为没有初始化,所以fat[2]=0,find(0)=0,fat[1]=0,这显然不对)

void uni(int x, int y)
{
    fat[x]=find(fat[y]);
}

这是罪魁祸首,其实写反了 

#include<bits/stdc++.h>
using namespace std;
 /*
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。
省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通
(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。
问最少还需要建设多少条道路?

 */
vector<int>fat(1001,0);
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        fat[i]=i;
    }
}

int find(int x)
{
    if(x==fat[x]){return fat[x];}
    return find(fat[x]);
}

void uni(int x, int y)
{
    fat[x]=find(fat[y]);
}

int main(){
    //要用并查集
    int n,m;
    while(cin>>n>>m)
    {   int x,y;
         
         set<int>s;
         //init(n);
         vector<int>v(n+1,0);
         for(int i=0;i<m;i++)
         {   cin>>x>>y;
              if(x==0||y==0){break;}
             uni(x,y);//并查集
              //uni(y,x);
             v[x]=1;v[y]=1;//将出现过的节点保存
         }
         //出现过的节点的父亲放入set,最后看set的size即可知道有几个连通图,还差几个
         //比如4个节点,有2个连通图,且包含了3个节点
         //那么把两个连通图连起来一条边,把第四个节点加入再一条边
         int num=count(v.begin(),v.end(),1);
         for(int i=1;i<=n;i++)
         {
             if(v[i]==1)//访问过,就判断属于哪个连通子图
             {
                 s.insert(find(i));
             }
         }
         int num_lt=s.size();
         auto it=s.begin();
         //cout<<fat[5]<<" "<<fat[6]<<fat[9]<<" "<<endl;
         //cout<<n<<" "<<num<<" "<<num_lt<<endl;
         cout<<n-num+num_lt-1<<endl;
        
    }
    
    return 0;
}

这次就AC了,大改一通

#include<bits/stdc++.h>
using namespace std;
 /*
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。
省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通
(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。
问最少还需要建设多少条道路?

 */
vector<int>fat(1001,0);

int find(int x)
{    int ans;
    if(fat[x]==0){return x;}
    else //不是0那么肯定有了父亲,那就把路径压缩一下直达父亲,当然不压缩也行,就是会慢一点
    {    ans=find(fat[x]);
        fat[x]=ans;
    }
        return ans;
}

void uni(int x, int y)
{
    if(find(x)!=find(y)){//本就在一起就不用连起来了,当然不剪枝也行,也是会慢一点
        //int x=find(x);
        fat[find(x)]=find(y);}//要把xy各自最顶层的连接起来,不能fat[x]=find(y),这样会连不起来
    
}

int main(){
    //要用并查集
    int n,m;
    while(cin>>n>>m)
    {    int x,y;

         for(int i=1;i<=n;i++)
         {
             fat[i]=0;
         }
     
         vector<int>v(n+1,0);
         for(int i=0;i<m;i++)
         {   cin>>x>>y;
              if(x==0||y==0){break;}
             uni(x,y);//并查集
         }
     
         int num=0;
         for(int i=1;i<=n;i++)
         {
             if(fat[i]==0)//没有成别人的子节点或者自己就是根或者孤立
             {num++;
             }
         }
         cout<<num-1<<endl;
        
    }
    
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值