介绍
不相交集合主要就是说,同一个元素只能存在于一个集合对象中,而不能存在于多个对象集合当中。主要的操作有生成集合对象,查找,以及合并操作。
不相交集合的表示方法有多样,下图是链表表示法:
![linked-list_disjointSet](https://img-blog.csdnimg.cn/img_convert/ff4b3ca122dfc777a9edcbc82bcac425.png)
每个集合的由链表构成,成员head和tail,head指针链表的头结点,tail指向链表的尾结点。而每个结点的有一个指向下一个结点的指向和一个指向head的指针。
在上图中,如果我们要合并s1和s2,则可以使用启发示的权重合并,我们在每个链表当中增加一个属性,length来记录每个链表的长度,则每次合并的时候把短的链表合并到长的链表当中,则可以减少操作所需要的时间。
而另一种表示方法则是森林表示法:
![forest_disjointSet](https://img-blog.csdnimg.cn/img_convert/7d9bf57deccbc9b3adb1035c19f3a52b.png)
每个结点有一个指向父结点的指针,则根结点则指向它自身。优化森林表示法的方法有2种,一种是给每个结点增加一个rank来表达当前结点所在的子结点大小,然后合并是,把rank低的合并到rank高的树中去,相同,等把一个合并到另一个去,并++rank的值。另一种是路径压缩,在查找结点时,把当前结点直接指向根结点。最有效率的方法是把2种方法一起使用,使程序运行时间为线性。
实现
![connectedComponent](https://img-blog.csdnimg.cn/img_convert/c85c8f3ab182ef15550a1c6580800026.png)
图中连通分支的实现:
struct _Node;
typedef struct _Vertex
{
int vertex;
int rank;
struct _Node *p;
}Vertex;
typedef struct _Node
{
Vertex *pVertex;
struct _Node *next;
}Node;
typedef struct _AdjacencyList
{
Node *node;
}AdjacencyList;
这里定义每个顶点有一个顶点编号,一个rank,即它的子结点大小,还有一个p,则是记录路径中它的父结点的指针,这里把p放在顶点属性中而没有放在结点属性中。
接着就是图的初始化和创建的动作:
Node* initNode(AdjacencyList *graph, int vertex)
{
Node *p = new Node;
p->pVertex = graph[vertex - 1].node->pVertex;
p->next = nullptr;
return p;
}
void InsertVertex(AdjacencyList *graph, Node **node, int vertex)
{
if (nullptr == *node)
{
*node = initNode(graph, vertex);
}else
{
Node *p = *node;
while (nullptr != p->next)
{
p = p->next;
}
p->next = initNode(graph, vertex);
}
}
void initGraph(AdjacencyList *list, int vertexCount)
{
for (int i = 0; i < vertexCount; ++ i)
{
list[i].node = new Node;
list[i].node->next = nullptr;
list[i].node->pVertex = new Vertex;
list[i].node->pVertex->vertex = i + 1;
list[i].node->pVertex->rank = 0;
list[i].node->pVertex->p = nullptr;
}
}
给每个顶点设置一个集合:
void makeSet(Node *x)
{
x->pVertex->p = x;
x->pVertex->rank = 0;
}
因为自己是根结点,于是把自己设置成自己的父结点,并把rank置0。
![makeSet初始化集合](https://img-blog.csdnimg.cn/img_convert/c79b7200fbb666cfaefbec617630d74f.png)
makeSet初始化集合
void link(Node *x, Node *y)
{
if (x->pVertex->rank > y->pVertex->rank)
{
y->pVertex->p = x;
}else if (x->pVertex->rank < y->pVertex->rank)
{
x->pVertex->p = y;
}
else if (y->pVertex->p == y)
{
y->pVertex->p = x;
if (x->pVertex->rank == y->pVertex->rank)
{
++ x->pVertex->rank;
}
}else
{
x->pVertex->p = y;
if (x->pVertex->rank == y->pVertex->rank)
{
++ y->pVertex->rank;
}
}
}
void unionSet(Node *x, Node *y)
{
link(x, y);
}
合并2个结点的集合时,把rank小的合并到rank大的集合中,如果相等,则要判断2个是否是根结点,把是根结点的集合合并到非根结点的集合中。
![union合并集合](https://img-blog.csdnimg.cn/img_convert/73d299279b2d1b62e0a3d37e5c716e39.png)
union合并集合
Node* findSet(Node *x)
{
if (x != x->pVertex->p)
{
x->pVertex->p = findSet(x->pVertex->p);
}
return x->pVertex->p;
}
findSet用来查询当前结点的父结点。它会递归往上查找,直到查找到父结点为止。并且把所有路径上的结点的父结点修改成根结点。
照上上面的图所写的例子如下:
int _tmain(int argc, _TCHAR* argv[])
{
AdjacencyList graph[VERTEX_NUMBER] = {0};
initGraph(graph, VERTEX_NUMBER);
InsertVertex(graph, &(graph[0].node), 2);
InsertVertex(graph, &(graph[0].node), 3);
InsertVertex(graph, &(graph[1].node), 1);
InsertVertex(graph, &(graph[1].node), 3);
InsertVertex(graph, &(graph[1].node), 4);
InsertVertex(graph, &(graph[2].node), 1);
InsertVertex(graph, &(graph[2].node), 2);
InsertVertex(graph, &(graph[3].node), 2);
InsertVertex(graph, &(graph[4].node), 6);
InsertVertex(graph, &(graph[4].node), 7);
InsertVertex(graph, &(graph[5].node), 5);
InsertVertex(graph, &(graph[6].node), 5);
InsertVertex(graph, &(graph[7].node), 9);
InsertVertex(graph, &(graph[8].node), 8);
InsertVertex(graph, &(graph[9].node), 10);
for (int i = 0; i < VERTEX_NUMBER; ++ i)
{
Node *p = graph[i].node;
makeSet(p);
}
for (int i = 0; i < VERTEX_NUMBER; ++ i)
{
Node *p = graph[i].node->next;
while (nullptr != p)
{
if (findSet(graph[i].node) != findSet(graph[p->pVertex->vertex - 1].node))
{
unionSet(graph[i].node, graph[p->pVertex->vertex - 1].node);
}
p = p->next;
}
}
for (int i = 0; i < VERTEX_NUMBER; ++ i)
{
cout << "the same component with " << i + 1 << ": ";
for (int j = 0; j < VERTEX_NUMBER; ++ j)
{
if (findSet(graph[i].node) == findSet(graph[j].node)
&& i != j)
{
cout << j + 1 <<" ";
}
}
cout << endl;
}
return 0;
}
从结果:
![disjointSet](https://img-blog.csdnimg.cn/img_convert/62847365c0d535222876a707ad807fc1.png)
可以看出,1,2,3,4为一个集合,5,6,7为一个集合,8,9为一个集合,10单独为一个集合。它们是互相不相交的。