并查集
来源:互联网 发布:淘宝提前收款条件 编辑:程序博客网 时间:2024/06/06 23:51
转自维基百科
并查集
在计算机科学中,并查集是一种树型的数据结构,其保持着用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(union-find algorithm)定义了两个操作用于此数据结构:
Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。
因为它支持这两种操作,一个不相交集也常被称为联合-查找数据结构(union-find data structure)或合并-查找集合(merge-find set)。其他的重要方法,MakeSet,用于建立单元素集合。有了这些方法,许多经典的划分问题可以被解决。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着。Find(x)返回x所属集合的代表,而Union使用两个集合的代表作为参数。
并查集森林
并查集森林是一种将每一个集合以树表示的数据结构,其中每一个节点保存着到它的父节点的引用(见意大利面条堆栈)。
在并查集森林中,每个集合的代表即是集合的根节点。“查找”根据其父节点的引用向根行进直到到底树根。“联合”将两棵树合并到一起,这通过将一棵树的根连接到另一棵树的根。实现这样操作的一种方法是:
function MakeSet(x) x.parent := x
function Find(x) if x.parent == x return x else return Find(x.parent)
function Union(x, y) xRoot := Find(x) yRoot := Find(y) xRoot.parent := yRoot
这是并查集森林的最基础的表示方法,这个方法不会比链表法好,这是因为创建的树可能会严重不平衡;然而,可以用两种办法优化。
第一种方法,称为“按秩合并”,即总是将更小的树连接至更大的树上。因为影响运行时间的是树的深度,更小的树添加到更深的树的根上将不会增加秩除非它们的秩相同。在这个算法中,术语“秩”替代了“深度”,因为同时应用了路径压缩时(见下文)秩将不会与高度相同。单元素的树的秩定义为0,当两棵秩同为r的树联合时,它们的秩r+1。只使用这个方法将使最坏的运行时间提高至每个MakeSet、Union或Find操作O(log n)。
优化后的MakeSet和Union伪代码:
function MakeSet(x) x.parent := x x.rank := 0
function Union(x, y) xRoot := Find(x) yRoot := Find(y) if xRoot == yRoot return // x和y不在同一个集合,合并它们。 if xRoot.rank < yRoot.rank xRoot.parent := yRoot else if xRoot.rank > yRoot.rank yRoot.parent := xRoot else yRoot.parent := xRoot xRoot.rank := xRoot.rank + 1
第二个优化,称为“路径压缩”,是一种在执行“查找”时扁平化树结构的方法。关键在于在路径上的每个节点都可以直接连接到根上;他们都有同样的表示方法。为了达到这样的效果,Find递归地经过树,改变每一个节点的引用到根节点。得到的树将更加扁平,为以后直接或者间接引用节点的操作加速。
这儿是Find的伪代码:
function Find(x) if x.parent != x x.parent := Find(x.parent) return x.parent
这两种技术可以互补,可以应用到另一个上,每个操作的平均时间仅为O(a(n)),a(n)是n = f(x) = A(x,x)的反函数,并且A是急速增加的阿克曼函数。因为a(n)是其的反函数,a(n)对于可观的巨大n还是小于5。因此,平均运行时间是一个极小的常数。
实际上,这是渐近最优算法。
例题:
tyvj1017 代码:
#include<cstdio>#define MAXN 1010int n,m;int f[MAXN];int find(int x){ if(f[x]==x)return x; f[x]=find(f[x]); return f[x];}void merge(int x,int y){ int rx=find(x),ry=find(y); f[rx]=ry;}int main(){ freopen("ty1017.in","r",stdin); freopen("ty1017.out","w",stdout); scanf("%d%d",&n,&m); for(int i=0;i<=m+10;i++)f[i]=i; int x,y,ans=0; for(int i=1;i<=n;i++) { scanf("%d%d",&x,&y); if(find(x)==find(y)) ans++; else merge(x,y); } printf("%d\n",ans); return 0;}
HDU 4775 代码:
#include <stdio.h>#include <string.h>#include <queue>#include <map>using namespace std;#define MP(a,b) make_pair(a,b)const int N = 10005;const int d[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};int T, n;typedef pair<int, int> pii;map<pii, int> vi, vis;int parent[N], sum[N], x[N], y[N];int find(int x) { if (x == parent[x]) return x; return parent[x] = find(parent[x]);}void init() { scanf("%d", &n); vi.clear(); for (int i = 1; i <= n; i++) { parent[i] = i; sum[i] = 0; scanf("%d%d", &x[i], &y[i]); }}void del(int x, int y, int who) { queue<pii> Q; Q.push(MP(x, y)); vis.clear(); vis[MP(x,y)] = 1; while (!Q.empty()) { pii now = Q.front(); parent[vi[now]] = vi[now]; sum[vi[now]] = 0; vi.erase(now); Q.pop(); for (int i = 0; i < 4; i++) { int xx = now.first + d[i][0]; int yy = now.second + d[i][1]; if (xx <= 0 || yy <= 0 || vis[MP(xx,yy)]) continue; int tmp = vi[MP(xx,yy)]; if ((tmp&1)^who == 0) { vis[MP(xx,yy)] = 1; Q.push(MP(xx, yy)); } else { int pt = find(tmp); sum[pt]++; } } }}void solve() { for (int i = 1; i <= n; i++) { vi[MP(x[i],y[i])] = i; int empty = 0; for (int j = 0; j < 4; j++) { int xx = x[i] + d[j][0]; int yy = y[i] + d[j][1]; if (xx <= 0 || yy <= 0) continue; if (vi.count(MP(xx,yy)) == 0) { empty++; continue; } int pv = find(vi[MP(xx,yy)]); sum[pv]--; } sum[i] = empty; for (int j = 0; j < 4; j++) { int xx = x[i] + d[j][0]; int yy = y[i] + d[j][1]; if (xx <= 0 || yy <= 0) continue; if (vi.count(MP(xx,yy)) == 0) continue; if (((vi[MP(xx,yy)]&1)^(i&1)) == 0) { int pa = find(i); int pb = find(vi[MP(xx,yy)]); if (pa != pb) { parent[pa] = pb; sum[pb] += sum[pa]; } } else { int pv = find(vi[MP(xx,yy)]); if (sum[pv] == 0) del(xx, yy, vi[MP(xx,yy)]&1); } } int pv = find(i); if (sum[pv] == 0) del(x[i], y[i], i&1); } int ansa = 0, ansb = 0; vis.clear(); for (int i = n; i >= 1; i--) { if (vi.count(MP(x[i],y[i])) == 0 || vis[MP(x[i], y[i])]) continue; vis[MP(x[i],y[i])] = 1; if (vi[MP(x[i],y[i])]&1) ansa++; else { ansb++; } } printf("%d %d\n", ansa, ansb);}int main() { scanf("%d", &T); while (T--) { init(); solve(); } return 0;}
pair 是 一种模版类型。每个pair 可以存储两个值。这两种值无限制。也可以将自己写的struct的对象放进去。
应用:如果一个函数有两个返回值 的话,如果是相同类型,就可以用数组返回,如果是不同类型,就可以自己写个struct ,但为了方便就可以使用 c++ 自带的pair ,返回一个pair,其中带有两个值。除了返回值的应用,在一个对象有多个属性的时候 ,一般自己写一个struct ,如果就是两个属性的话,就可以用pair 进行操作,如果有三个属性的话,其实也是可以用的pair 的 ,极端的写法pair <int ,pair<int ,int > >
(后边的两个 > > 要有空格,否则就会是 >> 位移运算符)。
每个pair 都有两个属性值 first 和second cout<<p1.first<<p1.second;
map是一类关联式容器,它是模板类。关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置类获取。它的特点是增加和删除节点对迭代器的影响很小,除了操作节点,对其他的节点都没有什么影响。对于迭代器来说,不可以修改键值,只能修改其对应的实值。
定义:
map<int, string> personnel;
这样就定义了一个以int为键,值为string的map对象personnel。
map中定义了以下三个类型:
map<K, V>::key_type : 表示map容器中,索引的类型;map<K, V>::mapped_type : 表示map容器中,键所关联的值的类型;map<K, V>::value_type : 表示一个pair类型,它的first元素具有const map<K, V>::key_type类型,而second元素则有map<K,V>::mapped_type类型对迭代器进行解引用时,将获得一个引用,指向容器中一个value_type类型的值,对于map容器,其value_type是pair类型。
添加元素:
1. 使用下标操作符获取元素,然后给元素赋值
For example:
map<string, int> word_count; // 定义了一个空的map对象word_count; word_count["Anna"] = 1; 程序说明: 1.在word_count中查找键为Anna的元素,没有找到. 2.将一个新的键-值对插入到word_count中,他的键是const string类型的对象,保存Anna。而他的值则采用直初始化,这就意味着在本例中指为0. 3.将这个新的键-值对插入到word_count中 4.读取新插入的元素,并将她的值赋为1. 使用下标访问map与使用下标访问数组或者vector的行为是截然不同的:使用下标访问不存在的元素将导致在map容器中添加一个新的元素,他的键即为该下标值。
使用map::insert方法添加元素
map容器提供的insert操作:1. map.insert(e) : e是一个用在map中的value_type类型的值。如果键不存在,则插入一个值为e.second的新元素;如果键在map中已经存在,那么不进行任何操作。该函数返回一个pair类型,该pair类型的first元素为当前插入e的map迭代器,pair的second类型是一个bool类型,表示是否插入了该元素。 2. map.insert(beg, end) : beg和end是迭代器,返回void类型 3. map.insert(iter, e) : e是value_type类型的值,如果e.first不在map中,则创建新元素,并以迭代器iter为起点搜索新元素存储的位置,返回一个迭代器,指向map中具有给定键的元素。 For example: word_count.insert(map<sting, int>::value_type("Anna", 1)); word_count.insert(make_pair("Anna", 1)); 返回值:如果该键已在容器中,则其关联的值保持不变,返回的bool值为true。
查找并获取map中的元素
使用下标获取元素存在一个很危险的副作用:如果该键不在map容器中,那么下标操作会插入一个具有该键的新元素。
因此引入map对象的查询操作:
map.count(k) : 返回map中键k的出现次数(对于map而言,由于一个key对应一个value,因此返回只有0和1,因此可以用此函数判断k是否在map中)
map.find(k) : 返回map中指向键k的迭代器,如果不存在键k,则返回超出末端迭代器
For example:int occurs = 0;if( word_count.cout("foobar") ) occurs = word_count["foobar"];int occurs = 0;map<string, int>::iterator it = word_count.find("foobar");if( it != word_count.end() ) occurs = it ->second;
从map中删除元素
移除某个map中某个条目用erase()
该成员方法的定义如下:
- iterator erase(iterator it); //通过一个条目对象删除
- iterator erase(iterator first, iterator last); //删除一个范围
- size_type erase(const Key& key); //通过关键字删除
map对象的迭代遍历
与其他容器一样,map同样提供begin和end运算,以生成用于遍历整个容器的迭代器。
map注意事项
1. Map中的swap不是一个容器中的元素交换,而是两个容器交换: m1.swap( m2 );
2. Map中的元素是自动按key升序排序,所以不能对map用sort函数:
3. 7, map的基本操作函数:
C++ Maps是一种关联式容器,包含“关键字/值”对
``` begin() 返回指向map头部的迭代器 clear() 删除所有元素 count() 返回指定元素出现的次数 empty() 如果map为空则返回true end() 返回指向map末尾的迭代器 equal_range() 返回特殊条目的迭代器对 erase() 删除一个元素 find() 查找一个元素 get_allocator() 返回map的配置器 insert() 插入元素 key_comp() 返回比较元素key的函数 lower_bound() 返回键值>=给定元素的第一个位置 max_size() 返回可以容纳的最大元素个数 rbegin() 返回一个指向map尾部的逆向迭代器 rend() 返回一个指向map头部的逆向迭代器 size() 返回map中元素的个数 swap() 交换两个map upper_bound() 返回键值>给定元素的第一个位置 value_comp() 返回比较元素value的函数```
- HDU3938 并查集 并查集
- 并查集(集并查)
- HDU1232 并查集<并>
- 并查集
- 数据结构-并查集
- 并查集
- 并查集!
- 并查集
- 并查集
- 并查集
- 并查集
- 并查集总结
- 并查集学习
- 并查集
- 并查集
- 并查集
- 所谓并查集
- 并查集
- RTSPClient工具EasyRTSPClient支持H.265,支持海思等各种芯片平台
- 集成百度地图遇到的问题汇总
- 使用条件变量时为啥一定要指定一个锁?
- 解读(四):分析主界面顶部Tab的实现
- phpStorm 2016.1 最新版激活方法
- 并查集
- 七大排序算法初步实现
- 自己的一个demo用到的图标资源
- Kafka开发实战(一)-入门篇
- LintCode:删除元素
- 我是一个菜鸟程序员
- Error Domain=NSCocoaErrorDomain Code=3840
- 文本属性
- Android 异步加载图片-LruCache和SD卡或手机缓存-三级缓存原理加载图片