并查集

  • Post author:
  • Post category:其他




并查集

是一种特殊的集合,由一些不相交子集构成,合并查集的基本操作是:


Find:判断两个结点是否在同一个集合中

Union:归并两个集合




因此可以将并查集中

每个结点只保存一个指向其父结点的指针域,通过判断节点的父指针是否相同可以判断2个节点是否是同一个并查集




用父指针表示的树形结构实现的并查集可以很容易地解决等价类问题:



1、

约定森林F={T1,T2,…,Tr}表示集合S


2、

森林中的每一棵树Ti表示集合S的一个子集


3、

树中的结点表示集合S中的一个元素


4、

树中的每一个非根结点都指向其父结点,用根结点作为集合的标识符


5、

由于用根结点表示子集的类别,那么“查找”某一个元素所属的集合,只要从该结点出发,沿父链域找到树的根结点即可。


6、

实现集合的“并”操作只要将一棵子树的根指向另一棵子树的根即可





重量权衡合并规则





1、

每次合并前都需要进行两次查找,查找所需要的时间由树的高度决定,合并所需的时间为O(1)容易看出,在最坏情况下合并可能使n个结点的树退化成一条链


2、

为了防止树退化为单链,应该让每个结点到其相应根结点的距离尽可能小,可以做如下两种改进


2.1

在做“合并”操作之前先判别子集中所含成员的数目,然后令含成员少的子集的树根指向含成员多的子集的根,称作“重量权衡合并规则”(weightedunion rule)。把小树合并到大树中去,可以把树的整体深度限制在O(logn),每次Find操作只需要O(logn)时间

2.2


在执行Union时总是将小树并到大树上,而且在执行Find时实行路径压缩



ParTreeNode.h

#ifndef PAR_TREE_NODE_H
#define PAR_TREE_NODE_H

template <typename T>
class ParTreeNode //树结点定义
{
private:
	T value;//结点的值

	ParTreeNode<T> *parent;//父结点指针
	int nCount;//以此结点为根的子树的总结点个数

public:
	ParTreeNode()
	{
		parent = nullptr;
		nCount = 1;
	}

	~ParTreeNode()
	{

	}

	T getValue()//返回结点的值
	{
		return value;
	}

	void setValue(T v)//设置结点的值
	{
		value = v;
	}

	ParTreeNode<T> * getParent()//返回父结点指针
	{
		return parent;
	}

	void setParent(ParTreeNode<T> *parent)//设置父结点指针
	{
		this->parent = parent;
	}

	int getCount()//返回结点数目
	{
		return this->nCount;
	}

	void setCount(int count)//设置结点数目
	{
		this->nCount = count;
	}
};

#endif  PAR_TREE_NODE_H

ParTree.h


#ifndef PAR_TREE_H
#define PAR_TREE_H
#include "ParTreeNode.h"

template <typename T>
class ParTree//树定义
{
public:
	ParTreeNode<T> *array;//存储树结点的数组
	int size;//数组大小

	ParTree(const int size)
	{
		this->size = size;
		array = new ParTreeNode<T>[size];
	}

	~ParTree()
	{
		delete []array;
	}

	ParTreeNode<T> * find(ParTreeNode<T> *node)const//查找node结点的根结点
	{
		ParTreeNode<T> *point = node;
		while(point->getParent() != nullptr)
		{
			point = point->getParent();
		}

		return point;
	}

	void unionTree(int i,int j)//把下标为i,j的结点合并成一棵子树
	{
		ParTreeNode<T> *pointi = find(&array[i]);
		ParTreeNode<T> *pointj = find(&array[j]);

		if(pointi != pointj)
		{
			if(pointi->getCount() >= pointj->getCount())
			{
				pointj->setParent(pointi);
				pointi->setCount(pointi->getCount() + pointj->getCount());
			}
			else
			{
				pointi->setParent(pointj);
				pointj->setCount(pointi->getCount() + pointj->getCount());
			}
		}
	}
	
	ParTreeNode<T> * findPC(ParTreeNode<T> *node)const//查找node结点的根结点
	{
		if(node->getParent() == nullptr)
		{
			return node;
		}

		node->setParent(findPC(node->getParent()));
		return node->getParent();
	}

	void unionTreePC(int i,int j)//把下标为i,j的结点合并成一棵子树
	{
		ParTreeNode<T> *pointi = findPC(&array[i]);
		ParTreeNode<T> *pointj = findPC(&array[j]);

		if(pointi != pointj)
		{
			if(pointi->getCount() >= pointj->getCount())
			{
				pointj->setParent(pointi);
				pointi->setCount(pointi->getCount() + pointj->getCount());
			}
			else
			{
				pointi->setParent(pointj);
				pointj->setCount(pointi->getCount() + pointj->getCount());
			}
		}
	}

	bool diffrent(int i,int j)
	{
		return find(&array[i]) != find(&array[j]);
	}
};
#endif

main.cpp


#include "ParTree.h"
#include <iostream>

#define N 10
using std::cout;
using std::cin;
using std::endl;

char strInfo[N] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'};  //结点信息数组

// 函数功能:根据字符信息获得数字在数组中的位置下标
int getNum(char ch)  {
	for (int i = 0; i < N; i ++)
		if (ch == strInfo[i])
			return i;
	return -1;
}

// 函数功能:显示数据在父指针表示法中的父结点下标
void display(ParTree<char> &aParTree)  {
	for (int i = 0; i < N; i ++)  {
		if (aParTree.array[i].getParent() == NULL)            //如果是没有父结点就用*表示
			cout << "*" << " ";
		else  {
			char ch = aParTree.array[i].getParent()->getValue(); //如果有父结点就打印数组中的父结点的下标
			cout << getNum(ch) << " ";
		}
	}
	cout << endl;
}


#define PATHCOMPRESSION   1  
// 0-不带压缩
// 1-带压缩

int main()  {
	ParTree<char> aParTree(N);
	cout << "* means that the node has no parents!\n" ;

	for (int i = 0; i < N; i++)
		cout << i << " ";
	cout << endl;

	for (int i = 0; i < N; i++)  {
		aParTree.array[i].setValue(strInfo[i]);
		cout << strInfo[i] << " ";
	}
	cout << endl;

	cout << "Union: (A,B),(C,D),(E,F),(G,H),(I,J)\n" ;
	aParTree.unionTree(getNum('A'), getNum('B')); 
	aParTree.unionTree(getNum('C'), getNum('D'));
	aParTree.unionTree(getNum('E'), getNum('F'));
	aParTree.unionTree(getNum('G'), getNum('H'));
	aParTree.unionTree(getNum('I'), getNum('J'));
	display(aParTree);                             //显示数据在父指针表示法中的父结点下标

	cout << "\nUnion: (A,D),(I,H)\n";
	aParTree.unionTree(getNum('A'), getNum('D'));
	aParTree.unionTree(getNum('I'), getNum('H'));
	display(aParTree);								//显示数据在父指针表示法中的父结点下标

#if !PATHCOMPRESSION 
	// 0.不带压缩
	cout << "\nUnion: (F,J)\n" ;
	aParTree.unionTree(getNum('F'), getNum('J'));
	display(aParTree);								//显示数据在父指针表示法中的父结点下标
	cout << "Union: (D,J)\n";
	aParTree.unionTree(getNum('D'), getNum('J'));
	display(aParTree);								//显示数据在父指针表示法中的父结点下标

#else
	// 1.带压缩 
	cout << "\nUnion: (F,J)\n";
	aParTree.unionTreePC(getNum('F'), getNum('J'));
	display(aParTree);								//显示数据在父指针表示法中的父结点下标
	cout << "\nUnion: (D,J)\n";
	aParTree.unionTreePC(getNum('D'), getNum('J'));
	display(aParTree);								//显示数据在父指针表示法中的父结点下标
#endif

	if (aParTree.diffrent(getNum('D'), getNum('E')))
		cout << "D and E are in different sets!!!" << endl;
	else
		cout << "D and E are in a common set!!!" << endl;
	cout << endl;

	system("pause");
	return 0;
} 




版权声明:本文为ccshijtgc原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。