1.17 LeetCode总结(基本算法)_并查集

编程总结

每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧

并查集是一种树形数据结构。一般用来根据子节点快速找到其父节点,并且用于处理一些不交集(Disjoint Sets)的合并及查询问题。在算法面试中,常用来解决连通性问题。

核心思想
并查集的核心思想是使用集合中的一个元素来代表整个集合, 这个元素我们称之为代表元素.
查找操作即找这个集合中的代表元素, 如果两个元素的代表元素一样, 那么它们在同一个集合;
合并操作即找到两个集合的代表元素, 然后将其中一个的父节点设为另一个节点.

一般主要实现使用并查集的两个函数:
Find(x):确定元素x属于哪一个子集。
Union(a, b):将a和b所在的两个子集合并成同一个集合。

先明确几个概念
1.集合树:所有节点以代表节点为父节点构成的多叉树
2.节点的代表节点:可以理解为节点的父节点,从当前节点出发,可以向上找到的第一个节点
3.集合的代表节点:可以理解为根节点,意味着该集合内所有节点向上走,最终都能到达的节点

684. 冗余连接

在这里插入图片描述

来个图帮助理解
在这里插入图片描述
上图中是一棵集合树,树中有1-6总计6个节点
整个集合的代表节点是1
4节点的代表节点是3,6节点的代表节点是1
无论沿着哪个节点向上走,最终都会达到集合代表节点的1节点

然后具体到这个题上:
我们以这个边集合为例子[[1,2], [3,4], [3,2], [1,4], [1,5]]

一、首先,对于边集合edges的每个元素,我们将其看作两个节点集合
比如边[2, 3],我们将其看作节点集合2,和节点集合3

二、在没有添加边的时候,各个节点集合独立,我们需要初始化各个节点集合的代表节点为其自身
所以,我们先初始化一个容器vector,使得vector[i]=i
这里两个i意思不同,作为索引的i是指当前节点,作为值的i是指当前节点所在集合的代表节点
比如vector[2] = 2,意味着2这个节点所在集合的代表节点就是2,没有添加边的情况下,所有节点单独成集合,自身就是代表节点
初始化后,集合图如下图所示:
在这里插入图片描述

三、然后我们开始遍历边集合,将边转化为集合的关系
这里有一点很重要:边[a,b]意味着a所在集合可以和b所在集合合并。
合并方法很多,这里我们简单地将a集合的代表节点戳到b集合的代表节点上
这意味着,将b集合代表节点作为合并后大集合的代表节点
对于一个集合的代表节点s,一定有s->s,意思是s如果是代表节点,那么它本身不存在代表节点
假设我们的读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
初始化vector[0, 1, 2, 3, 4, 5]
对应的index[0, 1, 2, 3, 4, 5]

####################################################################

1.读取[1,2]:
读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
当前vector[0, 1, 2, 3, 4, 5]
当前index [0, 1, 2, 3, 4, 5]
原本1->1,2->2,
由1节点出发,vector[1]=1, 找到1所在集合的代表节点1
由2节点出发,vector[2]=2, 找到2所在集合的代表节点2
于是,将1的代表置为2,vector[1]=2, vector[2]=2
对应的vector[0, 2, 2, 3, 4, 5]
对应的index [0, 1, 2, 3, 4, 5]

原集合变为下图:
在这里插入图片描述
####################################################################

2.读取[3, 4]
读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
当前vector[0, 2, 2, 3, 4, 5]
当前index [0, 1, 2, 3, 4, 5]
同理,将3所在集合的的代表节点3的代表节点置为4
对应的vector[0, 2, 2, 4, 4, 5]
对应的index [0, 1, 2, 3, 4, 5]

集合变化如下图:
在这里插入图片描述
####################################################################

这里要注意找到根节点,所以有递归的调用,结束判断是 parent[index] == index为根节点

3.读取[3, 2]
读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
当前vector[0, 2, 2, 4, 4, 5]
当前index [0, 1, 2, 3, 4, 5]
从节点3出发,vector[3]=4, vector[4]=4,于是找到节点3所在集合的代表节点为4
从节点2出发,vector[2]=2, 找到节点2所在集合的代表节点为2
于是,将4的代表置为2,vector[4]=2, vector[2]=2
对应的vector[0, 2, 2, 4, 2, 5]
对应的index [0, 1, 2, 3, 4, 5]

集合变化如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/16c4133f4086428c81e0979eb20b81fb.png
####################################################################

4.读取[1, 4]
读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
当前vector[0, 2, 2, 4, 2, 5]
当前index [0, 1, 2, 3, 4, 5]
从节点1出发,vector[1]=2, vector[2]=2, 找到节点1所在集合代表节点为2
从节点4出发,vector[4]=2, vector[2]=2, 找到节点4所在集合代表节点为2
由于1和4的代表节点相同,说明这两个节点本身就在同一个集合中
由于原图是无向图,路径是双向可达的,1能够到达2,而且2能够到达4,再加上1能够到达4
说明1能通过两条路径到达4,,这也意味着这条边出现的时候,原图中一定出现了环
至于题中要求的,返回最后一条边,其实这就是返回添加过后会构成环的那一条边
直白解释就是,在这条边出现之前,图中没有环
这条边出现,图中也出现环。包括这条边在内,构成环的边都是满足破圈条件的边
然而谁是最后一条出现在边集合里的?当然,就是这条构成环的最后一条边
####################################################################

// 查找操作即找这个集合中的代表元素, 如果两个元素的代表元素一样, 那么它们在同一个集合;
// 这里要注意找到根节点,所以有递归的调用,结束判断是 parent[index] == index为根节点
// parent[5] = {1, 2, 3, 4, 5}, 这里每一个节点是根节点
int Find(int *parent, int index) {
	if (parent[index] != index) {
		parent[index] = Find(parent, parent[index]);
	}
	return parent[index];
}

// 合并操作即找到两个集合的代表元素, 然后将其中一个的父节点设为另一个节点.
void Union(int *parent, int index1, int index2)
{
	// 替换前一个根节点,合并起来
	parent[Find(parent, index1)] = Find(parent, index2);
}

int *findRedundantConnection(int **edges, int edgesSize, int *edgesColSize, int *returnSize)
{
	// 后面数组下标从1开始,便于和index对应上,所以要+1大小申请
	int *parent = (int *)malloc(sizeof(int) * (edgesSize + 1));

	// 一个元素来代表整个集合, 这个元素我们称之为代表元素
	// 如果两个元素的代表元素一样, 那么它们在同一个集合
	for (int i = 1; i <= edgesSize; ++i) {
		parent[i] = i;
	}

	for (int i = 0; i < edgesSize; ++i) {
		int node1 = edges[i][0], node2 = edges[i][1];
		if (Find(parent, node1) != Find(parent, node2)) {
			Union(parent, node1, node2);
		}
		else {
			*returnSize = 2;
			return edges[i];
		}
	}

	*returnSize = 0;
	return NULL;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值