并查集
来源:互联网 发布:直播软件apk 编辑:程序博客网 时间:2024/05/28 16:19
并查集
一、并查集是处理什么问题的:
并查集,是一种用来管理元素分组情况的数据结构,可以处理一些不相交集合的合并与查询问题;它可以进行合并操作,但不能进行分割操作。
二、两大操作:
(1)查询元素a和元素b是否属于同一集合;
(2)合并元素a和元素b所在的集合;
三、主要的步骤:
初始化:
把每个点所在集合初始化为其自身。
(通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。)
查找:
查找元素所在的集合,即根节点。
合并:
将两个元素所在的集合合并为一个集合。
基本思想:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中,最终就形成了多个(可能一个)不同的集合,其中每一个集合都具有不同于其它集合的属性,例如:每一个集合可以表示一个连通分支,则属于同一个集合的元素就表示这个连通分支的顶点,这个集合中的每一个顶点都是相互可达的,这样就可以就可以解决任意输入的两个元素是否可达的问题了。
基本思想:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中,最终就形成了多个(可能一个)不同的集合,其中每一个集合都具有不同于其它集合的属性,例如:每一个集合可以表示一个连通分支,则属于同一个集合的元素就表示这个连通分支的顶点,这个集合中的每一个顶点都是相互可达的,这样就可以就可以解决任意输入的两个元素是否可达的问题了。
四、处理并查集问题需要解决了的几个难点:
(1)如何合并两个不相交集合;
(2)如何判断两个元素是否属于同一个集合;
(3)路径压缩,优化时间。
优化方法:1、路径压缩:对每一个节点,一旦向上走到了一次根节点,就把这个节点到父亲的边改为直接连向根的边;
2、启发式合并:让让深度较小的树成为深度较大的树的子树。
五、并查集的结构:
并查集是使用树形结构实现的,不过,不是二叉树。每个元素对应一个节点,每个集合对应一棵树。
六、并查集实现中的注意点:(在使用路径压缩时,为了方便起见,即使数的高度发生了变化,我们也不修改rank的值)
在树形数据结构里,如果发生了退化的情况,那么复杂度就会变得很高。因此,有必要想办法避免退化的发生。在并查集中,只要按照如下方法就可以避免退化。
(1)对每棵树,记录这棵树的高度(rank).
(2)合并时如果两棵树的rank不同,那么从rank小的向rank大的连边。
我做的第一题并查集 HDU 1213;
点击打开链接
以下是代码部分:
#include <iostream>#include <stdio.h>#include <string.h>#define N 1000using namespace std;int father[N];int num;void set_mark(int n){ int i; for(i = 1; i <= n; i++) { father[i] = i; } return;}int getfather(int v){ int geng = v; int temp; while(geng != father[geng]) { geng = father[geng]; } while(geng != v) { temp = father[v]; father[v] = geng; v = temp; } return geng;}void Merge(const int &i, const int &j){ int x = getfather(i); int y = getfather(j); if(x != y) { father[x] = y; num -= 1; } return;}int main(){ int T; int n, m; cin >> T; while(T--) { int a, b; cin >> n; num = n; set_mark(n); cin >> m; for(int i = 0; i < m; i++) { cin >> a; cin >> b; Merge(a, b); } cout << num << endl; } return 0;}
以下每一块是一个模板(分四个函数标记、找根、合并、查找)
int father[maxn];int n;void Init() { for (int i = 0; i < n; ++i) { father[i] = i; }}// 递归法int getFather(const int &v) { if (father[v] == v) return v; else return getFather(father[v]);}// 递归另一种写法。int getFather(const int &v) { if (father[v] != v) { int root = getFather(father[v]); return father[v] = root; } else return v;}// 非递归,且不带路径压缩。int getFather(const int &v) { int r = v; while(r != father[r]) r = father[r]; return r;}*/// 非递归,路径压缩int getFather(const int &v) { int t1 = v, t2; while (v != father[v]) v = father[v]; while (t1 != father[t1]) { // 沿途上所有结点的父亲改成根。这一步是顺便的,不增加时间复杂度,却使得今后的操作比较快。这个优化称为路径压缩。 t2 = father[t1]; father[t1] = v; t1 = t2; } return v;}// 归并:把节点i、节点j放到同一个根底下。void merge(const int &i, const int &j) { int x = getFather(i); int y = getFather(j); if (x != y) // 可选,主要是为了防止getFather()路径压缩的时候出现死循环。 father[x] = y; // 有向图注意顺序,该行代码含义:a->b。}// 查询:查询节点i跟节点j是否在同一根下。bool judge(const int &i, const int &j) { if (getFather(i) == getFather(j)) return true; else return false;}
优化思路:
merge函数可以采用启发式合并,思路就是把深度较小的那颗子树并到深度较大的那颗子树上。int father[maxn];int n;void Init() { for (int i = 0; i <= n; ++i) { rank[i] = 0; father[i] = i; }}int getFather(const int &x) { int px = x , i ; while ( px != father[px]) // find root px = father[px]; while ( x != px ) { // path compression i = father [ x ]; father [ x ] = px ; x = i; } return px ;}void merge(const int &x, const int &y) { // 下面还有一种写法 x = getFather(x); y = getFather(y); if (rank[x] > rank[y]) father[y] = x; else { father[x] = y; if (rank[x] == rank[y]) rank[y]++; } }bool judge(const int &i, const int &j) { if (getFather(i) == getFather(j)) return true; else return false;}
int father[maxn], rank[maxn];void Init(const int &v) { father[v] = -1; rank[v] = 0;}int getFather(const int &v) { int t1 = v; while (father[t1] != -1) t1 = father[t1]; while (v!=t1) { int t2 = father[v]; father[v] = t1; v = t2; } return t1; } void merge(const int &a, const int &b) { int t1 = getFathet1(a); int t2 = getFathet1(b); if(rank[t1] > rank[t2]) father[t2] = t1; else father[t1] = t2; if(rank[t1] == rank[t2]) ++rank[t2]; }bool judge(const int &i, const int &j) { if (getFather(i) == getFather(j)) return true; else return false;}
/* 另一种写法:*/int f[maxn], rank[maxn], num[maxn];void Init() { for (int i = 0; i <= n; ++i) { rank[i] = 1; num[i] = 1; father[i] = i; }}// f[]数组存放根节点,rank[]数组来存放根节点的深度,num[]数组来存放节点个数,rank[]数组和num[]数组的初始化都应为1// 启发式合并:void merge(int x, int y){ fx = getFather(x); fy = getFather(y); if (fx == fy) return; if (rank[fx] > rank[fy]) { father[fy] = fx; num[fx] += num[fy]; } else { father[fx] = fy; num[fy] += num[fx]; if (rank[fx] == rank[fy]) { ++rank[fy]; } }}// 路径压缩:int getFather(int x) { if(father[x] == x) return x; else return father[x] = getFather(father[x]);}
// 仍有一种写法:int father[maxn];void Init() { for(int i = 0; i < n; ++i) father[i] = -1;}int getFather(int x) { if (father[x] < 0) return x; father[x] = getFather(father[x]); return father[x];}int getFather(int x) { int p = x, t; while (father[p] >= 0) p = father[p]; while (x != p) { t = father[x]; father[x] = p; x = t; } return x;}void merge(int x, int y) { x = getFather(x); y = getFather(y) if (x == y) return; if (father[x] < father[y]) { father[x] += father[y]; father[y] = x; } else { father[y] += father[x]; father[x] = y; }}bool judge(const int &i, const int &j) { if (getFather(i) == getFather(j)) return true; else return false;}
1 0
- HDU3938 并查集 并查集
- 并查集(集并查)
- HDU1232 并查集<并>
- 并查集
- 数据结构-并查集
- 并查集
- 并查集!
- 并查集
- 并查集
- 并查集
- 并查集
- 并查集总结
- 并查集学习
- 并查集
- 并查集
- 并查集
- 所谓并查集
- 并查集
- 整了一个编程相关的导航
- STM32F746 利用HAL库编写串口驱动程序
- native2ascii命令详解
- selenium的webdriverAPI使用操作多项选择框内容_10_3.docx
- 1.7顺序输出1到100之间所有能被7整除的整数
- 并查集
- 005.PHP实现二分查找
- sdutacm-图结构练习——BFS——从起始点到目标点的最短步数
- 《嵌入式系统软硬件协同设计实战指南》第九章跑马灯实验补充
- Python学习笔记之python基础(一)
- xml ---dom4j
- 面积
- vs中动态链接库和静态链接库的创建及使用
- Win64 驱动内核编程-3.内核里使用内存