【数据结构】——树的应用之并查集

假设我们有一些数据集合,我们对这些集合有俩种操作:

(1)给出俩个数据x,y,把x所在的集合和y所在的集合进行合并。

(2)给出俩个数据x,y,询问x与y是否在同一个集合中。

如何进行存储和表示呢?

存储:采用父亲表示法,由于涉及到集合直接用数组下标就表示某个数,故不用结构体。

合并:寻找俩个数据的根节点,让一个根结点做另一个根节点的孩子。

查询:寻找俩个数据的根节点进行比较。如果相同则在同一棵树中,不同则不在同一棵树中。

一、时间复杂度为O(n)

#include <iostream>
int tree[105];//父亲表示法,t[x]=y----->x的父亲是y
int n, m;//n个点,m次操作

int find(int x) //查找根节点
{
	if (tree[x] == x) return x; //因为下面操作是开始令自己的根节点都是自己本身
	else return find(tree[x]); //查找自己父亲的根节点
}

int main()
{
	std::cin >> n >> m;
	for (int i = 1; i <= n; ++i)
	{
		tree[i] = i;//开始令父亲为自己本身
	}
	int q, x, y;
	while (m--)
	{
		std::cin >> q >> x >> y;
		int xi, yi;
		if (q == 1) // q=1代表合并
		{
			xi = find(x); //查找它的根节点
			yi = find(y);
			tree[xi] = yi;//tree[yi]=xi;
		}
		else //q=2代表查询
		{
			xi = find(x);
			yi = find(y);
			if (xi == yi)std::cout << "YES\n";
			else std::cout << "NO\n";
		}
	}
	return 0;
}

在find()函数中可知,如果当一颗树为斜树时,它最坏就是查找完所有的数据从而找到根节点,因此时间复杂度为O(n)。

二、时间复杂度为O(logn)

#include <iostream>

int tree[105];//父亲表示法,t[x]=y----->x的父亲是y
int h[105];//结点的高度
int n, m;//n个点,m次操作

int find(int x) //查找根节点
{
	if (tree[x] == x) return x;
	else return find(tree[x]);
}

int main()
{
	std::cin >> n >> m;
	for (int i = 1; i <= n; ++i)
	{
		tree[i] = i;//开始令父亲为自己本身
		h[i] = 1; //开始高度都为1
	}
	int q, x, y;
	while (m--)
	{
		std::cin >> q >> x >> y;
		int xi, yi;
		if (q == 1) // q=1代表合并
		{
			xi = find(x); //查找它的根节点
			yi = find(y);
			if (h[xi] >= h[yi])
			{
				tree[yi] = xi; //让根节点高度低的树做根节点高度高的树的孩子
				h[yi] = std::max(h[yi], h[xi] + 1);//更新根结点的高度
			}
			else
			{
				tree[xi] = yi;
				h[xi] = std::max(h[xi], h[yi] + 1);
			}
		}
		else //q=2代表查询
		{
			xi = find(x);
			yi = find(y);
			if (xi == yi)std::cout << "YES\n";
			else std::cout << "NO\n";
		}
	}
	return 0;
}

引入高度数组,数组中存在每个结点的高度。在每次合并时都比较俩个数据的根节点的高度,始终把高度低的根节点去做高度高的根节点的孩子,使得树保持完全二叉树形状,这样树的高度就降下来为logn。查找最慢就是O(logn)。

三、时间复杂度为常数型:O(α(n))

#include <iostream>
int tree[105];//父亲表示法,t[x]=y----->x的父亲是y
int n, m;//n个点,m次操作

int find(int x) //查找根节点
{
	if (tree[x] == x) return x;
	else
	{
		int f = find(tree[x]); //因为find函数是查找根节点,所以返回值是一定是根节点
		tree[x] = f;  //路劲压缩:每次使结点都直接连根节点
		return f;
	}
}

int main()
{
	std::cin >> n >> m;
	for (int i = 1; i <= n; ++i)
	{
		tree[i] = i;//开始令父亲为自己本身
	}
	int q, x, y;
	while (m--)
	{
		std::cin >> q >> x >> y;
		int xi, yi;
		if (q == 1) // q=1代表合并
		{
			xi = find(x); //查找它的根节点
			yi = find(y);
			tree[xi] = yi;
		}
		else //q=2代表查询
		{
			xi = find(x);
			yi = find(y);
			if (xi == yi)std::cout << "YES\n";
			else std::cout << "NO\n";
		}
	}
	return 0;
}

使得查询速度更快的就是引入路劲压缩思想。n个结点的树高度最小的情况就是2,即一个结点为根节点,其他结点都为根节点的孩子。这样每次查询都是常数级的。在查找次数少时,查询还是会从父亲开始查,但当查询次数多时候,最终所有的结点都直接连上根节点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值