UFSet并查集的定义与实现

上篇写到集合,干脆把并查集一块写了。


一:什么是并查集

并查集是一种用途广泛的集合,也称为disjoint set,它支持一下3种操作:

1. Union(Root1, Root2):把子集合Root2并入到集合Root1中。要求Root1和Root2互不相交,否则执行合并。

2.Find(x):搜索单元素x所在的集合,并返回该集合名字。

3.UFSets(s):构造函数,将并查集中s个元素初始化为s个只有一个单元素的子集和。

二:并查集的表示

我们一般用数组和树表示,如图(图非原创):


负值表示根结点,负多少表示有多少个元素。普通结点的值为根节点下标。如图元素7,值为0,所以根节点为0号元素,它的值为-4,说明包括它该集合共4个元素。图中有

0,6,7,8。

如果想直观理解路径,可以把数组画成树的形式,类似下图。


三:并查集主要操作

并查集主要有以下操作,分别用伪代码描述,后文会给出正式代码。

1.Union


UNION(root1,root2)
    parent[root1] <- parent[root1] + parent[root2]
    parent[root2] <- root1


功能是对两个集合进行合并,root必须是根节点。所以我们要想造成下图这种路径压缩前的情况,必须倒着进行并集才可能产生。测试代码中会有一个错误的演示。


必须Union(1,0),Union(2,1).....Union(4,3)这样操作,每个子元素才可能把个数叠加到最终个根节点4,并且每个子元素都指向正确的parent。

2.FIND

(1).iteration


FIND(x)    //iteration
    while parent[x] >= 0 do
x <- parent[x]
    return x


(2).recursion


FIND(x)    //recursion
    if parent[x] < 0  return x
    else FIND(parent[x])


3.Weighted_Union

按照权重来并,我们总是把小的并到大的的根节点,采用加权规则,这样能一定程度防止并查集产生退化的树


WEIGHTED_UNION(root1,toot2)
    temp <- parent[root1] + parent[root2]
    if parent[root1] - parent[root2] < 0   //parent1 < parent2
parent[root1] <- temp
parent[root2] <- root1
    else
parent[root2] <- temp
parent[root1] <- root2


4.压缩路径

按照折叠规则压缩路径,在包含元素idea树中搜索根,并将从i到根的路径上所有结点都变成根的子女。



COLLAPSINGFIND(i)
    j <- i
    while parent[j] >= 0 do
j <- parent[j]
    while parent[i] ≠ j do
temp <- parent[i]
parent[i] <- j
i <- temp


四:代码实现:

#ifndef _UF_SETS_H
#define _UF_SETS_H

#include <iostream>
#include <vector>
#include <assert.h>
#include <algorithm>

#define _DEBUG_
#define _RECURSION_

const int DEFAULT_SIZE = 10;

class uf_sets {
public:	
	uf_sets(int sz = DEFAULT_SIZE);
	~uf_sets();
public:
	uf_sets& operator=(uf_sets& other);
	void set_union(int root1, int root2);
	int  find(int x);
	void weighted_union(int root1, int root2);
	void collapsing_find(int i);
#ifdef _DEBUG_
	void show() const;
#endif
private:
	int              size_;
	std::vector<int> parent_;
};

uf_sets::uf_sets(int sz)
{
	size_ = sz;
	parent_.resize(size_);
	std::fill(parent_.begin(), parent_.end(), -1);
}

uf_sets::~uf_sets()
{
}

uf_sets& uf_sets::operator=(uf_sets& other)
{
	std::copy(other.parent_.begin(), other.parent_.end(), parent_.begin());
	size_ = other.size_;
}

void uf_sets::set_union(int root1, int root2)
{
	parent_[root1] += parent_[root2];
	parent_[root2] = root1;
}

#ifndef _RECURSION_
int uf_sets::find(int x)
{
	assert(x < size_);
	while(parent_[x] >= 0)
		x = parent_[x];
	return x;
}

#else
int uf_sets::find(int x)
{
	if(parent_[x] < 0)
		return x;
	else
		find(parent_[x]);
}
#endif

void uf_sets::weighted_union(int root1, int root2)
{
	int temp = parent_[root1] + parent_[root2];
	if(parent_[root1]-parent_[root2] < 0){
		parent_[root1] = temp;
		parent_[root2] = root1;
	}
	else{
		parent_[root2] = temp;
		parent_[root1] = root2;
	}
}

void uf_sets::collapsing_find(int i)
{
	assert(i < size_);
	int j = i;
	while(parent_[j] >= 0)
		j = parent_[j];
	while(parent_[i] != j){
		int temp = parent_[i];
		parent_[i] = j;
		i = temp;
	}
}

void uf_sets::show() const
{
	for_each(parent_.begin(), parent_.end(), [] (int n) 
									{ std::cout<<n<<' '; });
	std::cout<<std::endl;
}

#endif

测试文件:

#include "uf_sets.h"

#include <iostream>
using namespace std;

int main()
{
#if 0
	//test simple
	uf_sets us(10);
	us.show();

	us.set_union(0, 6);
	us.set_union(0, 7);
	us.set_union(0, 8);

	us.set_union(1, 4);
	us.set_union(1, 9);

	us.show();

	cout<<us.find(6)<<endl;
	cout<<us.find(4)<<endl;

	us.weighted_union(1, 0);
	us.show();
	cout<<us.find(1)<<endl;
#endif
	//test collapsingfind
	uf_sets us(10);
	
/*
	//error
	us.set_union(0, 6);      //cann't set union do like this, see set_union function !
	us.set_union(0, 7);      //正向Union无法产生路径压图中长路径,因为Union函数不支持,它是针对两个root实现的
	us.set_union(0, 8);
	us.set_union(7, 1);
	us.set_union(7, 9);
	us.set_union(9, 3);
	us.set_union(9, 5);
*/	

	us.set_union(9, 5);
	us.set_union(9, 3);
	us.set_union(7, 9);
	us.set_union(7, 1);
	us.set_union(0, 6);
	us.set_union(0, 7);
	us.set_union(0, 8);

	us.show();

	us.collapsing_find(5);
	us.show();


	return 0;
}

更详细解释参考:并查集详解 (转),一个很幽默风趣的解释。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值