并查集
2016-04-28 00:58
507 查看
转自维基百科
Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。
因为它支持这两种操作,一个不相交集也常被称为联合-查找数据结构(union-find data structure)或合并-查找集合(merge-find set)。其他的重要方法,MakeSet,用于建立单元素集合。有了这些方法,许多经典的划分问题可以被解决。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着。Find(x)返回x所属集合的代表,而Union使用两个集合的代表作为参数。
在并查集森林中,每个集合的代表即是集合的根节点。“查找”根据其父节点的引用向根行进直到到底树根。“联合”将两棵树合并到一起,这通过将一棵树的根连接到另一棵树的根。实现这样操作的一种方法是:
这是并查集森林的最基础的表示方法,这个方法不会比链表法好,这是因为创建的树可能会严重不平衡;然而,可以用两种办法优化。
第一种方法,称为“按秩合并”,即总是将更小的树连接至更大的树上。因为影响运行时间的是树的深度,更小的树添加到更深的树的根上将不会增加秩除非它们的秩相同。在这个算法中,术语“秩”替代了“深度”,因为同时应用了路径压缩时(见下文)秩将不会与高度相同。单元素的树的秩定义为0,当两棵秩同为r的树联合时,它们的秩r+1。只使用这个方法将使最坏的运行时间提高至每个MakeSet、Union或Find操作O(log n)。
优化后的MakeSet和Union伪代码:
第二个优化,称为“路径压缩”,是一种在执行“查找”时扁平化树结构的方法。关键在于在路径上的每个节点都可以直接连接到根上;他们都有同样的表示方法。为了达到这样的效果,Find递归地经过树,改变每一个节点的引用到根节点。得到的树将更加扁平,为以后直接或者间接引用节点的操作加速。
这儿是Find的伪代码:
这两种技术可以互补,可以应用到另一个上,每个操作的平均时间仅为O(a(n)),a(n)是n = f(x) = A(x,x)的反函数,并且A是急速增加的阿克曼函数。因为a(n)是其的反函数,a(n)对于可观的巨大n还是小于5。因此,平均运行时间是一个极小的常数。
实际上,这是渐近最优算法。
例题:
tyvj1017 代码:
HDU 4775 代码:
pair 是 一种模版类型。每个pair 可以存储两个值。这两种值无限制。也可以将自己写的struct的对象放进去。
应用:如果一个函数有两个返回值 的话,如果是相同类型,就可以用数组返回,如果是不同类型,就可以自己写个struct ,但为了方便就可以使用 c++ 自带的pair ,返回一个pair,其中带有两个值。除了返回值的应用,在一个对象有多个属性的时候 ,一般自己写一个struct ,如果就是两个属性的话,就可以用pair 进行操作,如果有三个属性的话,其实也是可以用的pair 的 ,极端的写法
每个pair 都有两个属性值 first 和second
map是一类关联式容器,它是模板类。关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置类获取。它的特点是增加和删除节点对迭代器的影响很小,除了操作节点,对其他的节点都没有什么影响。对于迭代器来说,不可以修改键值,只能修改其对应的实值。
定义:
这样就定义了一个以int为键,值为string的map对象personnel。
map中定义了以下三个类型:
添加元素:
1. 使用下标操作符获取元素,然后给元素赋值
For example:
使用map::insert方法添加元素
map容器提供的insert操作:
查找并获取map中的元素
使用下标获取元素存在一个很危险的副作用:如果该键不在map容器中,那么下标操作会插入一个具有该键的新元素。
因此引入map对象的查询操作:
map.count(k) : 返回map中键k的出现次数(对于map而言,由于一个key对应一个value,因此返回只有0和1,因此可以用此函数判断k是否在map中)
map.find(k) : 返回map中指向键k的迭代器,如果不存在键k,则返回超出末端迭代器
从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是一种关联式容器,包含“关键字/值”对
并查集
在计算机科学中,并查集是一种树型的数据结构,其保持着用于处理一些不相交集合(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 1010 int 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 , sum , x , y ; 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的函数```
相关文章推荐
- android Google Map获取地理位置信息的方法
- Spark RDD API详解(一) Map和Reduce
- Python中map()函数浅析
- Android使用Google Map浅谈
- 书评:《算法之美( Algorithms to Live By )》
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- Lua中ipair和pair的区别
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- Erlang中的映射组Map详细介绍
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法