并查集

并查集

维护一些不相交的集合,它是一个集合的集合。每个元素恰好属于一个集合,好比每条鱼装在一个鱼缸里。每个集合S有一个元素作为\集合代表"rep[S],好比每个鱼缸选出一条"鱼王"。并查集提供三种操作:
MakeSet(x):建立一个新集合x。x应该不在现有的任何一个集合中出现。
Find(S, x):返回x所在集合的代表元素。
Union(x, y):把x所在的集合和y所在的集合合并。

森林表示法

可以用一棵森林表示并查集,森林里的每棵树表示一个集合,树根就是集合的代表元素。一个集合还可以用很多种树表示,只要树中的结点不变,表示的都是同一个集合。

合并操作

只需要将一棵树的根设为另一棵即可。这一步显然是常数的。一个优化是:把小的树合并到大树中,这样会让深度不太大。这个优化称为启发式合并.

查找操作

只需要不断的找父亲,根就是集合代表。一个优化是把沿途上所有结点的父亲改成根。这一步是顺便的,不增加时间复杂度,却使得今后的操作比较快。这个优化称为路径压缩。

下面是最基本得并查集代码,牢记,用得多了,写这种常用数据结构应该做到倒背如流,才可应对ACM中出现的各种变形 !!!

复制代码
  
  
const int SIZE = 100 ; int p[SIZE],rank_deep[SIZE]; // rank_deep[]记录该集合的深度
// 可以根据题目设置不同意义的rank数组 void makeset( int x){ p[x] = x; rank_deep[x] = 0 ; } // 非递归的方式进行路径压缩,更加直观一些 int findSet( int x){ int px = x,i; // find root while (px != p[px])px = p[px]; // path compression while (x != px){ i = p[x]; p[x] = px; x = i; } return px; } void unionSet( int x, int y){ x = findSet(x); y = findSet(y); // 原则:小的接到大的后面 if (rank_deep[x] > rank_deep[y]) p[y] = x; else { p[x] = y; // deep的定义为多分枝的最远那一条 if (rank_deep[x] == rank_deep[y])rank_deep[y] ++ ; } }
复制代码

实际应用:

POJ-1611 http://poj.org/problem?id=1611

复制代码
  
  
/* 并查集的基本应用——POJ1611 */ /* *********************************************************************** 大致题意:一共有n个学生(编号0 至 n-1),m个组,一个学生可以同时加入不同的组。 现在有一种传染病,如果一个学生被感染,那么和他同组的学生都会被感染。现在已 知0号学生被感染,问一共有多少个人被感染。 首先将每个学生都初始化为一个集合,然后将同组的学生合并,设置一个数组num[] 来记录每个集合中元素的个数,最后只要输出0号学生所在集合中元素的个数即可。 ************************************************************************* */ #include < iostream > usingnamespace std; // num[]数组存储节点所在的集合的总个数 // father[]数组存储dina int num[ 30001 ],father[ 30001 ]; // 初始化用,把每一个点定义为一个集合 void makeSet( int x){ father[x] = x; // 定义跟节点的标志:即为与父亲数组的值相同 num[x] = 1 ; } /* ******************************************************** */ // 查找x元素所在的集合,返回根节点 // 并且采用递归方式压缩路径,使得每一个点都指向根节点 int findSet( int x){ if (x != father[x]) // 只有根节点的父亲节点才与自己的值相同 x = findSet(father[x]); return x; // 此时的x已经被层层修改为最根的那个节点 } void unionSet( int a, int b){ a = findSet(a); b = findSet(b); if (a == b) return ; // 在同一个集合中,直接退出 // 此if,else语句将小集合合并到大集合中 // 用来平衡树的左右形状,减少整体层数 if (num[a] <= num[b]){ father[a] = b; num[b] += num[a]; // 更新集合的个数 } else { father[b] = a; num[a] += num[b]; } } int main(){ int m,n; while (cin >> m >> n && (m != 0 || n != 0 )){ for ( int i = 0 ;i < m;i ++ )makeSet(i); int t,first,next; while (n -- ){ cin >> t >> first; for ( int j = 1 ;j < t;j ++ ){ cin >> next; unionSet(first,next); } } cout << num[findSet( 0 )] << endl; } return0; }
复制代码

POJ 2492 http://poj.org/problem?id=2492

复制代码
  
  
/* ********************************************************** */ // 并查集的应用推广 // POJ2492 /* ********************************************************** */ #include < iostream > usingnamespace std; // r[i] 0代表r[i]与i同性, 1代表i与r[i]异性 int n,p[ 2002 ],r[ 2002 ]; /* ********************************************************** */ int find( int x){ int temp = p[x]; if (x == p[x]) return x; p[x] = find(p[x]); // 首先要明白一点,程序已经从递归中跳出,p[x]已经修改到 // 根节点,也就是说x已经指向祖先,所以要寻找x与祖先的关系 // 关系修改的解释:根据递归的堆栈原理,可知,x的父亲与 // 祖先的关系(即r[temp])已经清楚(后进先出),而x与 // 父亲的关系也是知道的,那么类似于“关系传递”,便可分 // 析出x与祖先的关系 r[x] = (r[temp] == r[x] ? 0 : 1 ); return p[x]; } /* ********************************************************** */ void make(){ // 初始化 for ( int i = 0 ;i <= n;i ++ ){ p[i] = i; r[i] = 0 ; } } /* ********************************************************** */ void unionSet( int x, int y, int px, int py){ p[py] = px; // 关系修改解释: // 有一点要清楚,由题意知,x与y是异性的,那么 // x与x父亲的关系知道,y与与父亲的关系也知道 // 求x父亲与y父亲关系便显而易见了 r[py] = r[x] == r[y] ? 1 : 0 ; } /* ********************************************************** */ int main(){ int i,j,m,a,b,px,py,flag,count = 1 ,t; scanf( " %d " , & t); while (t -- ){ flag = 0 ; scanf( " %d%d " , & n, & m); make(); while (m -- ){ scanf( " %d%d " , & a, & b); // 由题意知,每输如的一对肯定是异性 px = find(a); py = find(b); // 如果px==py说明在前面的论断中x与y已经建立起了练习 // 而现在要通过r[a]是否等于r[b]来验证是否产生了矛盾 if (px == py && r[a] == r[b]) flag = 1 ; else unionSet(a,b,px,py); } if (flag == 1 ) printf( " Scenario #%d:\nSuspicious bugs found!\n " ,count ++ ); else printf( " Scenario #%d:\nNo suspicious bugs found!\n " ,count ++ ); printf( " \n " ); } return0; } /* ******************************************************** */
复制代码

POJ 1182http://poj.org/problem?id=1182

复制代码
 
 
/* ******************************************************* 此道题目 前天看的时候一点头绪都没有,看了他人的解题报告后 也几乎看不懂,但是首先做了两道并查集的基础题目POJ1611,与 POJ2524,熟悉并查集的结构,又做了两道并查集的拓展题目 POJ2492与1703,,在充分了解并可以熟悉运用并查集后,此题 便可迎刃而解了,也通过此题发现了自学的诀窍“循序渐进”, 刚刚AC掉食物链问题,有些小激动,遂发此感慨,呵呵~~~ ******************************************************* */ #include < iostream > usingnamespace std; int N,K,relation,a,b,count; int p[ 50010 ],r[ 50010 ]; // p[]为父亲数组, // r[]为x与p[x]的关系数组 // 0代表同类,1代表x吃父亲p[x] // 1代表父亲p[x]吃x /* ***************************************************** */ // 初始化函数 void make(){ for ( int i = 1 ;i <= N;i ++ ){ p[i] = i; r[i] = 0 ; } } /* **************************************************** */ // 查找根节点(祖宗)的函数,采用递推压缩路径 // 使得每一个节点都指向祖宗 int find( int x){ int temp = p[x]; // p[x]之后要被修改,先记下x的父亲值 if (x == p[x]) return p[x]; p[x] = find(p[x]); // 递推压缩路径,都指向了根节点 r[x] = (r[x] + r[temp]) % 3 ; // 关系修改 return p[x]; } /* ***************************************************** */ // 合并 void unionSet( int x, int y, int px, int py){ p[px] = py; r[px] = (r[y] - r[x] + 3 + relation - 1 ) % 3 ; // 关于三处的关系修改于判断,本人一开始采用枚举的方式, // 不过如果枚举的话,每次使用倒要if(3*3=)九次,很是 // 啰嗦,之后总结出公式,不过可以用进行严格向量法证明 /* if(relation==1){ //r[px]=(r[y]-r[x])%3; if(r[x]==r[y]){r[px]=0;return;} if(r[x]==0){r[px]=r[y];return;} if(r[y]==0){r[px]=r[x];return;} if(r[x]==1){r[px]=1;return;} if(r[y]==1){r[px]=2;return;} } if(relation==2){ //r[px]=(r[y]-r[x]+1)%3; if(r[x]==0&&r[y]==0){} if(r[x]==0&&r[y]==1){} if(r[x]==0&&r[y]==2){} if(r[x]==0&&r[y]==) */ } /* ***************************************************** */ int main(){ cin >> N >> K; int pa,pb; count = 0 ; make(); while (K -- ){ scanf( " \n%d%d%d " , & relation, & a, & b); pa = find(a); pb = find(b); // 输入数据有矛盾可以直接判断 if (a > N || b > N || (relation == 2 && a == b)){count ++ ; continue ;} // pa!=pb,则说明,a与b还未建立关系,所以肯定不会 // 产生矛盾 if (pa != pb){unionSet(a,b,pa,pb); continue ;} // 如果产生关系,但是根据已存在的r[a]与r[b]算出的 // a与b之间的关系与输入的relation不相符,则矛盾 if ((r[a] - r[b] + 3 ) % 3 != (relation - 1 )){count ++ ; continue ;} } cout << count << endl; }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值