并查集

来源:互联网 发布:淘宝提前收款条件 编辑:程序博客网 时间: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容器中添加一个新的元素,他的键即为该下标值。
  1. 使用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()

该成员方法的定义如下:

  1. iterator erase(iterator it); //通过一个条目对象删除
  2. iterator erase(iterator first, iterator last); //删除一个范围
  3. 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的函数```
0 0