超超有爱爱-----并查集~~~

 感谢---华东理工大学---罗勇军教授

先看一下这篇(有周芷若童鞋哟),再看这篇效果更好哦  

  • 并查集(Disjoint Set):一种非常精巧而实用的数据结构。  
  • 用于处理不相交集合的合并问题。
  • 经典应用:
  1. 连通子图
  2. 最小生成树Kruskal算法
  3. 最近公共祖先

    应用背景:“帮派”

  • 一个城市中有n个人,他们属于不同的帮派;
  • 已知这些人的关系,例如1号、2号是朋友,1号,
  • 3号也是朋友,那么他们都属于一个帮派;
  • 问有多少帮派,每人属于哪个帮派。
  •   @用并查集可以很简洁地表示这个关系。

 例:hdu 1213 How Many Tables

  • 有n个人一起吃饭,有些人互相认识。
  • 认识的人想坐在一起,而不想跟陌生人坐。
  • 例如A认识B,B认识C,那么A、B、C会坐在一张桌子上。
  • 给出认识的人,问需要多少张桌子。

 并查集的操作

  • 初始化
  • 合并
  • 查找

 (1)初始化

  • 定义int s[]是以结点i为元素的并查集。
  •  初始化:令s[i]=i。 “一人一帮,我就是我!”(某人的号码是i,他属于帮派s[i]。

 

 (2)合并

  •  例:加入第一个朋友关系(1, 2)。
  •  在并查集s中,把结点1合并到结点2,
  • 也就是把结点1的集1改成结点2的集2。

 

  •  加入第二个朋友关系(1, 3):
  • 查找结点1的集,是2,递归查找元素2的集是2;
  • 把元素2的集2合并到结点3的集3。此时,
  • 结点1、2、3都属于一个集。

 

  •  加入第三个朋友关系(2, 4):

 

 (3)查找

  • 查找元素的集,是一个递归的过程,直到
  • 元素的值和它的集相等,就找到了根结点的集。
  • 这棵搜索树,可能很深,复杂度是O(n),
  • 变成了一个链表,出现了树的“退化”现象。
void init_set(){                       //初始化       
   for(int i = 1; i <= maxn; i++)
        s[i] = i;
}
void union_set(int x, int y){    //合并
    x = find_set(x);
    y = find_set(y);
    if(x != y) s[x] = s[y];
}
int find_set(int x){                   //查找   (递归)
    return x==s[x]? x:find_set(s[x]);
}

有多少个集(帮派)?

  •  如果s[i] = i,这是一个根结点,是它所在的集的代表;
  • 统计根结点的数量,就是集的数量。

        

 复杂度

  • 查找find_set()、合并union_set()的搜索深度
  • 是树的长度,复杂度都是O(n)。
  • 性能差。
  • 能优化吗?
  •      -目标:优化之后,复杂度 < O(logn)。

 @合并的优化

  •  合并元素x和y时,先搜到它们的根结点;
  • 合并这两个根结点:把一个根结点的集改成另一个根结点。
  • 这两个根结点的高度不同,把高度较小的集合并
  • 到较大的集上,能减少树的高度。

 

 

int height[maxn];         //用height[i]定义元素i的高度
void init_set(){   
   for(int i = 1; i <= maxn; i++){
        s[i] = i;
        height[i]=0;       //初始化树的高度
   }
}
void union_set(int x, int y){    //优化合并操作
    x = find_set(x);
    y = find_set(y);
    if (height[x] == height[y]) {
        height[x] = height[x] + 1;  //合并,树的高度加1
        s[y] = x;       
    }
    else{      //把矮树并到高树上,高树的高度保持不变
        if (height[x] < height[y])  s[x] = y;
        else   s[y] = x;
    }
}

查询的优化:路径压缩

  •  查询程序find_set():沿着搜索路径找到根结点,这条路径可能很长。
  • 优化:沿路径返回时,顺便把i所属的集改成根结点。下次再搜,复杂度是O(1)。

 

 路径压缩:递归实现

int find_set(int x){
    if(x != s[x]) 
	  s[x] = find_set(s[x]); //路径压缩
    return s[x];
}
  • 路径压缩:整个搜索路径上的元素,在递归过程中,
  • 从元素i到根结点的所有元素,它们所属的集都被改为根结点。
  • 路径压缩不仅优化了下次查询,而且也优化了合并,因为合并时也用到了查询。

 路径压缩:非递归实现

 如果数据规模太大,用递归担心爆栈,可以这样写:

int find_set(int x){
    int r = x;
    while ( s[r] != r ) r=s[r]; //找到根结点
    int i = x, j;
    while(i != r){   
         j = s[i]; //用临时变量j记录
         s[i]= r ; //把路径上元素的集改为根结点
         i = j;
    }
    return r;
}

并查集习题

  1.  poj 2524  Ubiquitous Religions,并查集简单题。
  2. poj 1611 The Suspects,简单题。
  3. poj 1703 Find them, Catch them。
  4. poj 2236 Wireless Network。
  5. poj 2492 A Bug's Life。
  6. poj 1988 Cube Stacking。
  7. poj 1182食物链,经典题。
  8. hdu 3635 Dragon Balls。
  9. hdu 1856 More is better。
  10. hdu 1272 小希的迷宫。
  11. hdu 1325 Is It A Tree。
  12. hdu 1198 Farm Irrigation。
  13. hdu 2586 How far away,最近公共祖先,并查集+深搜。
  14. hdu 6109 数据分割,并查集+启发式合并。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值