并查集

转自维基百科

并查集

在计算机科学中,并查集是一种树型的数据结构,其保持着用于处理一些不相交集合(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[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的函数```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值