这是一道模板题,因此我们把基础概念捋清,本题就水到渠成。并查集是跟图,树相关的一种算法。首先了解一下什么叫图:在数学中,图是描述于一组对象的结构,其中某些对象对在某种意义上是“相关的”。这些对象对应于称为顶点的数学抽象(也称为节点或点),并且每个相关的顶点对都称为边(也称为链接或线)。通常,图形以图解形式描绘为顶点的一组点或环,并通过边的线或曲线连接。 图形是离散数学的研究对象之一。——FROM百度百科。从图的定义中我们可以得知,图就是一些相关的元素组成的集合,因此自然与并查集有关了。“并”自然指的是合并,而“查"就是指查找,”集“就是指集合了。另外,树这种数据结构也是我们了解并查集必备的知识,就不讲解了。合并是需要树的,因为不同的集合推举出来某个根,在合并的时候根与根还要有依附的关系,路径压缩也需要根的知识。我们借助本题看看并查集的本质。加注释的行列是我之前的错误想法,有启发思维和借鉴的意义,所以我没删,我由此进一步说明并查集的本质。在注释中有”认爹“等词语,是我在网上看的别人的武侠风格的讲解并查集的帖子,很有意思,与代码结合可以加深理解。还有按秩合并,我这个蒟蒻尚且没看代码实现,只说路径压缩吧。。。
另:并查集的核心函数就是find merge
#include<stdio.h> #include<iostream> #include<string> #include<string.h> #include <algorithm> #include <cstdio> using namespace std; int a[2000]; //int a1[2000]; int b[2000]; //int e[2000]; //int f[2000]; int p[2000]; int find(int x)//查找+路径压缩。 find函数的本质是找祖宗(根)的过程。 { if(p[x]==x)return x;//这就是最原始的根了,找到了,找到了(尖叫状) else return p[x]=find(p[x]);//他爹=他祖宗,直接一步到位! 有啥好处呢,下一次查询的时候多快啊,一下子就把他爹(祖宗)揪出来了,方便比较。(这里用到递归,我们可以多从功能层面去关注,p[x]相当于他爹,find函数是找祖宗) } void merge(int x,int y) { int m,n; m=find(x); n=find(y); if(m!=n)p[find(x)]=find(y);//祖宗的依附过程,y的祖宗变成了x祖宗的爹,.1的本质 } int main() { int jilu,j,t,w,k,cnt=0; cin>>t; int z; w=0; for(z=0;z<t;z++) { cin>>a[z]>>b[z]; // a1[z]=a[z]; p[z]=z; } // for(k=0;k<t-1;k++)//这一段碎碎念可以自动略过。。我错误的根源其实就是不理解.1的本质:祖宗依附,而我先前以为是单一元素的依附,还想用排序来解决,行不通,还在认错爹认新爹乱认爹那里纠结,还想特殊标记操作。。。耗了我数个小时,屡次有问同学的冲动,幸好没问,我当时真是傻的可以。。所以冲动之前一定要三思啊,尽量别问,若问了就是唐氏! // { jilu=k; // for(j=k+1;j<t;j++) // { // if(a[j]<a[k]) // { // swap(a[j],a[k]); // jilu=j; // } // } // e[w]=a1[jilu]; // f[w]=b[jilu]; // w++; // } for(k=0;k<t-1;k++) { for(j=k+1;j<t;j++) { if(a[k]==a[j]||b[k]==b[j]) { merge(k,j); } } } for(k=0;k<t;k++) { if(p[k]==k)cnt++;//让他们自己当自己爹。 }//每个点都是独立的,有t个图。 cout<<cnt-1<<endl;//cnt个图需要cnt-1个点给他们串起来 system("pause"); return 0; }
总之,在代码实现新奇算法的旅通中,总会有各种各样的惊吓和惊喜,而没有人可以代替你领略这番风光。