不相交集合

介绍

    不相交集合主要就是说,同一个元素只能存在于一个集合对象中,而不能存在于多个对象集合当中。主要的操作有生成集合对象,查找,以及合并操作。

    不相交集合的表示方法有多样,下图是链表表示法:linked-list_disjointSet

linked-list_disjointSet

    每个集合的由链表构成,成员head和tail,head指针链表的头结点,tail指向链表的尾结点。而每个结点的有一个指向下一个结点的指向和一个指向head的指针。

    在上图中,如果我们要合并s1和s2,则可以使用启发示的权重合并,我们在每个链表当中增加一个属性,length来记录每个链表的长度,则每次合并的时候把短的链表合并到长的链表当中,则可以减少操作所需要的时间。

    而另一种表示方法则是森林表示法:forest_disjointSet

forest_disjointSet

    每个结点有一个指向父结点的指针,则根结点则指向它自身。优化森林表示法的方法有2种,一种是给每个结点增加一个rank来表达当前结点所在的子结点大小,然后合并是,把rank低的合并到rank高的树中去,相同,等把一个合并到另一个去,并++rank的值。另一种是路径压缩,在查找结点时,把当前结点直接指向根结点。最有效率的方法是把2种方法一起使用,使程序运行时间为线性。

实现

connectedComponent

connectedComponent

    图中连通分支的实现:

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初始化集合

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合并集合

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

disjointSet

    可以看出,1,2,3,4为一个集合,5,6,7为一个集合,8,9为一个集合,10单独为一个集合。它们是互相不相交的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Z小偉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值