一学就废的并查集它来了

    科技2022-07-16  101

    文章目录

    题目描述输入输出样例输入样例输出提示算法思想代码实现寻找根节点汇总连接情况完整代码关于flag的初值

    题目描述

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


    输入

    测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。 注意:两个城市之间可以有多条道路相通也就是说 3 3 1 2 1 2 2 1 这种输入也是合法的 当N为0时,输入结束,该用例不被处理。


    输出

    对每个测试用例,在1行里输出最少还需要建设的道路数目。


    样例输入

    4 2 1 3 4 3 3 3 1 2 1 3 2 3 5 2 1 2 3 5 999 0 0


    样例输出

    1 0 2 998


    提示

    Huge input scanf is recommended.


    算法思想

    数组下标作为一个点,数组中存储的元素作为其连接的另一个点,全联通即:将所有下标对应的点归拢到一个点上(也就是除了根节点,大家对应的都不是自己啦),相当于大家都跟一个点连接的话,当然是全联通状态啦。


    代码实现


    寻找根节点

    定义一个函数用来搜索给出结点的根节点,最终返回根节点的值(康康大家是不是来自同一地方嘛)

    int find(int x){ while(data[x]!=x){ x=data[x]; } return x;//返回根节点的值 }

    当联通关系不多,树的深度不深的时候,用上一种方法无疑是很快的,但是如果联通关系错综复杂,树的深度很深,例如每层只有一个节点这种奇葩情况,遍历搜索根节点就会很费时间,这时候就要用到“路径压缩“这一方法,即将所查询节点的数组内容由”父节点“改为”祖宗节点“。

    int find(int x){//寻找根节点 int ans,tmp; ans = x; while(data[x]!=x){//x是根节点 x=data[x]; } while (ans != x) {//路径压缩 tmp = data[ans]; data[ans] = x;//数组内容直接储存根节点,不再存储父节点 ans = tmp;//接下来压缩父节点 } return x;//返回根节点的值 }

    汇总连接情况

    将所给的两个点的连接情况进行记录,根节点不一样的话说明是第一次告知两个节点联通,所以要把根节点改成一样的, 一样的话当然就是告知过了,不需要改变(大型认祖归宗活动,哦兄弟我们是一家的,来来来族谱上记一下/哦兄弟我们是一家的,来来来……桥豆麻袋!族谱上记过了……不用记了诶) ⬆(淦!你忘了我们很久以前见过面的!!!)

    void add(int x, int y){ if(find(x)!=find(y)){ data[find(x)]=find(y); } }

    完整代码

    #include <iostream> #include <cstring> #include <algorithm> using namespace std; int data[1003]; int find(int x){//寻找根节点 while(data[x]!=x){ x=data[x]; } return x;//返回根节点的值 } void add(int x, int y){ if(find(x)!=find(y)){ data[find(x)]=find(y); } } int main(int argc, char const *argv[]) { int n,m;//n元素个数,m元素连接条数 int x,y;//x,y是两个相连的元素 int flag;//未联通的结点数 while (cin >> n,n) { memset(data,0,sizeof (data)); for (int i = 1; i <= n; i++) { data[i] = i; } for (cin >> m; m > 0; m--) { cin >> x; cin >> y; add(x,y); } flag = -1; for (int i = 1; i <= n; i++) { if(data[i]==i){ flag++;//数组内容等于数组下标表示和自己联通 } } cout << flag << endl; } return 0; }

    关于flag的初值

    其实难以理解的地方可能是flag的初值为什么要设置成-1,flag存储的是数组内容等于数组下标的情况(自己连接自己的情况)有多少,每有一个就说明有一种连接种类,全联通状态只有一个这样的情况——即根节点,也就是除了根节点和自己连接,其他的节点都不和自己连接,所以要把根节点这个情况排除在外,即flag初值为-1。 这样一来,当全联通时,只有根节点的data[i]=i,flag++,flag的值就为0,最后输出flag的值也就是还需要修多少条路。

    举个栗子可能更方便理解 当输入为 5 2 1 2 4 5 时 此时data数组的情况是(第四行): 连接种类有三种:根节点为2的1、2连接、根节点为3的自己和自己连接、以及根节点为5的4、5连接。 此时data[i]=i的情况会出现3次,flag的终值也就是-1自加三次,也就是2,表示还需要修两条路,举个例子,3 2,3 4这样修,那会出现什么情况呢?(为了便于理解拆分一下这个过程) 3 2的时候,3将2的根节点(也就是2本身)认作祖宗,3 4的时候,经过find函数查看族谱,诶大家祖宗不一样诶但我们是一家人,那就一个祖宗认另一个祖宗为爹好了,这样大家就在一起了,于是经过add函数修改族谱,3他爹2就认4他爹5当爹,1和3就成了5的乖孙孙。大家就是一家人了mua。(谁认谁当祖宗就看你怎么写了哦吼,data[find(x)]=find(y)是后面的数当前面的数的祖宗,data[find(y)]=find(x)就是前面的当祖宗咯)

    Processed: 0.014, SQL: 8