不相交集数据结构的链表表示
先决条件:联合查找(或不相交集)、不相交集数据结构(Java实现)
不相交集数据结构维护不相交动态集的集合 S = {S 1 , S 2 ,...., S k }。我们通过一个代表标识每个集合,它是集合的某个成员。在某些应用程序中,使用哪个成员作为代表并不重要;我们只关心如果我们两次请求一个动态集合的代表而不修改请求之间的集合,我们两次得到相同的答案。其他应用程序可能需要预先指定的规则来选择代表,例如选择集合中最小的成员。
例子:
确定无向图的连通分量。下图显示了一个具有四个连通分量的图。
解决方案:接下来的一个过程 X 使用不相交集操作来计算图的连通分量。一旦 X 对图进行了预处理,过程 Y 就会回答有关两个顶点是否在同一个连通分量中的查询。下图显示了处理每条边后不相交集的集合。
请参阅此处,因为上面的示例已在前面讨论过。
图 (a)两个集合的链表表示。集合 S1 包含成员 d、f 和 g,代表 f,而集合 S2 包含成员 b、c、e 和 h,代表 c。列表中的每个对象都包含一个集合成员、一个指向列表中下一个对象的指针和一个指向集合对象的指针。每个集合对象都有指向第一个和最后一个对象的指针 head 和 tail。 (b) UNION(e, g) 的结果,将包含 e 的链表追加到包含 g 的链表。结果集的代表是 f 。 e 的列表 S2 的集合对象被销毁。以上三个数字取自 Cormen(CLRS) 一书。上图显示了一种实现不相交集数据结构的简单方法:每个集都由自己的链表表示。每个集合的对象都有属性 head,指向列表中的第一个对象,和 tail,指向最后一个对象。
列表中的每个对象都包含一个集合成员、一个指向列表中下一个对象的指针和一个指向集合对象的指针。在每个链表中,对象可以按任何顺序出现。代表是列表中第一个对象中的集合成员。
为了执行 MAKE-SET(x),我们创建了一个新的链表,它的唯一对象是 x。对于 FIND-SET(x),我们只需跟随 x 的指针回到它的 set 对象,然后返回 head 指向的对象中的成员。例如,在图中,调用 FIND-SET(g) 将返回 f。
算法:
让 x 表示一个对象,我们希望支持以下操作:
MAKE-SET(x)创建一个新集合,它的唯一成员(因此代表)是 x。由于集合是不相交的,我们要求 x 不已经在其他集合中。
UNION (x, y)将包含 x 和 y 的动态集合(例如 S x和 S y )合并为一个新集合,该集合是这两个集合的并集。我们假设这两个集合在操作之前是不相交的。结果集的代表是 S x US y 的任何成员,尽管 UNION 的许多实现特别选择 S x或 S y的代表作为新代表。由于我们要求集合中的集合是不相交的,因此在概念上我们销毁集合 S x和 S y ,将它们从集合 S 中移除。实际上,我们经常将其中一个集合的元素吸收到另一个集合中。
FIND-SET(x)返回一个指向包含 x 的(唯一)集合的代表的指针。
基于上面的解释,下面是实现:
// C++ program for implementation of disjoint
// set data structure using linked list
#include
using namespace std;
// to represent linked list which is a set
struct Item;
// to represent Node of linked list. Every
// node has a pointer to representative
struct Node
{
int val;
Node *next;
Item *itemPtr;
};
// A list has a pointer to head and tail
struct Item
{
Node *hd, *tl;
};
// To represent union set
class ListSet
{
private:
// Hash to store addresses of set representatives
// for given values. It is made global for ease of
// implementation. And second part of hash is actually
// address of Nodes. We typecast addresses to long
// before storing them.
unordered_map nodeAddress;
public:
void makeset(int a);
Item* find(int key);
void Union(Item *i1, Item *i2);
};
// To make a set with one object
// with its representative
void ListSet::makeset(int a)
{
// Create a new Set
Item *newSet = new Item;
// Create a new linked list node
// to store given key
newSet->hd = new Node;
// Initialize head and tail
newSet->tl = newSet->hd;
nodeAddress[a] = newSet->hd;
// Create a new set
newSet->hd->val = a;
newSet->hd->itemPtr = newSet;
newSet->hd->next = NULL;
}
// To find representative address of a
// key
Item *ListSet::find(int key)
{
Node *ptr = nodeAddress[key];
return (ptr->itemPtr);
}
// union function for joining two subsets
// of a universe. Mergese set2 into set1
// and deletes set1.
void ListSet::Union(Item *set1, Item *set2)
{
Node *cur = set2->hd;
while (cur != 0)
{
cur->itemPtr = set1;
cur = cur->next;
}
// Join the tail of the set to head
// of the input set
(set1->tl)->next = set2->hd;
set1->tl = set2->tl;
delete set2;
}
// Driver code
int main()
{
ListSet a;
a.makeset(13); //a new set is made with one object only
a.makeset(25);
a.makeset(45);
a.makeset(65);
cout << "find(13): " << a.find(13) << endl;
cout << "find(25): "
<< a.find(25) << endl;
cout << "find(65): "
<< a.find(65) << endl;
cout << "find(45): "
<< a.find(45) << endl << endl;
cout << "Union(find(65), find(45)) \n";
a.Union(a.find(65), a.find(45));
cout << "find(65]): "
<< a.find(65) << endl;
cout << "find(45]): "
<< a.find(45) << endl;
return 0;
}
输出:
find(13): 0x1aa3c20
find(25): 0x1aa3ca0
find(65): 0x1aa3d70
find(45): 0x1aa3c80
Union(find(65), find(45))
find(65]): 0x1aa3d70
find(45]): 0x1aa3d70
注意:每次运行程序,节点地址都会改变。
MAKE-SET 和 FIND-SET 的时间复杂度是 O(1)。 UNION 的时间复杂度是 O(n)。
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。