您的位置:首页 > 大数据 > 人工智能

并查集

2016-04-28 00:58 507 查看
转自维基百科

并查集

在计算机科学中,并查集是一种树型的数据结构,其保持着用于处理一些不相交集合(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的函数```
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 并查集 map pair