原文地址:http://blog.csdn.net/ariesjzj/article/details/8001597
举个简单应用的例子。现在社交网站这么流行,假设现在想知道两个人之间是否存在间接好友关系(A和B为好友,B和C为好友,A和C为间接好友),有什么好方法呢?并查集就是用于这类查询问题的有效数据结构,正如其名(disjoint set),并查集本质上是一个集合,集合的元素为树,因此并查集实际上表示了一个森林(disjoint-set forests)。它的特点是每棵树中的成员都可由根结点所代表,这样要知道两个结点是否属于集合的同一元素,只要看它们是否有同一“代表”。
逻辑很简单,事实上原始的实现会比较慢,因为这儿的树并不保证平衡,极端情况下树会变成“条形”(每个结点只有一个孩子)。一般,我们会使用两种优化:称为union by rank和path compression。前者使集合在合并过程中尽可能平衡,后者使每个结点直接指向根结点,因此使查询在常数时间完成。根据这两种优化,下面是一个教科书式的实现:
int rank[100];
int p[100];
void makeSet(int x)
{
p[x] = x;
rank[x] = 0;
}
void unionSet(int x, int y)
{
linkSet(findSet(x), findSet(y));
}
void linkSet(int x, int y)
{
if (rank[x] > rank[y]) //union by rank
p[y] = x;
else {
p[x] = y;
if (rank[x] == rank[y])
rank[y] = rank[y] + 1;
}
}
int findSet(int x) {
if (x != p[x]) // path compression
p[x] = findSet(p[x]);
return p[x];
}
古人云,光说不练假把式,我们来看看这个看似简单的数据结构能解决点什么实际问题:
RoadReconstruction(Single Round Match 356 Round 1 - Division II, Level Three)
题目并不诡异,给出一些城市间的道路,有些是好的有些是破的,如果是破的还给出修的代价,问如何以最少的代价使所有城市全连通。咋一看是图论的最小生成树问题,其实。。。的确可以用最小生成树解,但这里我们尝试用并查集来解。注意解中利用了以下事实:初始时集合中有n棵树,每个树只有一个结点,自己“代表”自己。经过m轮合并,变成n-m个,经过n-1轮,合并为一个,则原森林中的所有结点全连通。
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <string>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
typedef struct damaged_node {
int cost;
string id;
int city1;
int city2;
} damaged_node;
typedef struct non_damaged_node {
int city1;
int city2;
} non_damaged_node;
bool compare(const damaged_node& r1, const damaged_node& r2)
{
return r1.cost < r2.cost;
}
int rank[100];
int p[100];
class RoadReconstruction {
public:
void makeSet(int x)
{
p[x] = x;
rank[x] = 0;
}
void unionSet(int x, int y)
{
linkSet(findSet(x), findSet(y));
}
void linkSet(int x, int y)
{
if (rank[x] > rank[y])
p[y] = x;
else {
p[x] = y;
if (rank[x] == rank[y])
rank[y] = rank[y] + 1;
}
}
int findSet(int x) {
if (x != p[x])
p[x] = findSet(p[x]);
return p[x];
}
string selectReconstruction (vector <string> roads)
{
int n = roads.size();
int i;
int cno = 0;
map<string, int> m; // map city name to no. in disjoint set
vector<damaged_node> damaged;
vector<non_damaged_node> non_damaged;
vector<string> res;
string ret = "";
// parse the arguments
for (i = 0; i < n; ++i) {
string id, city1, city2;
int cost = 0;
istringstream iss(roads[i]);
iss >> id >> city1 >> city2 >> cost;
if (m.find(city1) == m.end())
m[city1] = cno++; // generate unique id for each city
if (m.find(city2) == m.end())
m[city2] = cno++;
if (!cost) { // no cost, non damaged roads
non_damaged_node t = {m[city1], m[city2]};
non_damaged.push_back(t);
} else { // damaged roads
damaged_node t = {cost, id, m[city1], m[city2]};
damaged.push_back(t);
}
}
// init the disjoint set
for (i = 0; i < cno; ++i) {
makeSet(i);
}
int cnt = 0;
// reduce the disjoint set by considering non-damaged roads
for (i = 0; i < non_damaged.size(); ++i) {
int city1_no = non_damaged[i].city1;
int city2_no = non_damaged[i].city2;
findSet(city1_no); // update the parent, directly point to the representative
findSet(city2_no);
if (p[city1_no] != p[city2_no]) {
unionSet(city1_no, city2_no);
cnt++;
}
}
// try to reconstruct the damaged roads, in increasing order of cost
sort(damaged.begin(), damaged.end(), compare);
for (i = 0; i < damaged.size(); ++i) {
int city1_no = damaged[i].city1;
int city2_no = damaged[i].city2;
findSet(city1_no);
findSet(city2_no);
if (p[city1_no] != p[city2_no]) {
unionSet(city1_no, city2_no);
res.push_back(damaged[i].id);
cnt++;
}
}
if (cnt != cno - 1)
return "IMPOSSIBLE";
else { // all citys were merged into one.
sort(res.begin(), res.end());
for (i = 0; i < res.size(); ++i) {
if (i)
ret += " ";
ret += res[i];
}
return ret;
}
}
};