并查集(详细解释+完整C语言代码)

article/2025/8/27 2:50:58

目录

1概论

2.树的表现形式

3.代码实现

3.1创立集合

3.2合并

3.3查询

3.4路径压缩

第一个方法:查找时优化

第二个方法:合并时优化(加权标记法)

4.完整代码

4.1优化前

 4.2优化后


1概论

并查集是一种十分精巧且实用的树形数据结构,它主要处理一些不相交集合的合并与查询问题。

  • 不相交集合:即两集合交集为空。集合(1,2,3)与集合(4,5,6)不相交;
  • 合并:将两不相交集合合并为一个集合。集合被合并为(1,2,3,4,5,6)
  • 查询:查找此元素所处集合以及代表,可用于判断两元素是否同属一个集合

每个集合里有很多元素,我们选取其中一个元素作为此集合的代表。

  • 我们不关心这个集合中具体包含了哪些元素;
  • 我们不关心选哪个元素作为代表;
  • 我们关心的是,给定一个元素,可以很快找到这个元素所在集合的代表;
  • 我们关心的是,给定两个元素,判断他们是否处于同一个集合,如果不是,可以将两元素分别所处集合进行合并。

我们为什么关心这些,不关心那些呢?

因为这就是并查集的精妙之处,它可以解决很多实际问题。比如下面两道题:

我们以第二题为例,可以看出:只有两人之间(A与B)知道彼此是朋友,A不知道B与其他人的关系,B也不知道A与其他人的关系,甚至他们也不知道可能拥有共同的朋友C。题目假设朋友的朋友就是自己的朋友,相当于这群人是一伙的。现在,我想知道哪些人是一伙的。并查集就能轻松解决这个问题。

2.树的表现形式

并查集的实现原理也比较简单,就是用树来表示一个集合,树的每个结点就是集合的一个元素,根节点就是集合的代表。

 例如上图的两棵树,就对应的两个集合。我们继续用“朋友”的例子进行说明:刚才的朋友还知道彼此是朋友。而并查集实现时,就最多只知道自己的一个朋友,且不能是彼此知道。例如:D知道他的朋友是B,但B不知道D,只知道他的朋友是A。

以第一个集合为例:

  • D知道他的朋友为B,E知道他的朋友也为B,但D、E互相不认识;
  • B知道他的朋友为A,C知道他的朋友也为A,但B、C互相不认识,同时B也不知他还有朋友D、E;
  • A作为这个集合的代表,只知道自己,不知道他“下面”的朋友们。

尽管A~E是一伙的,他们却最多知道自己的一个朋友?那怎样才能让两人相见时,知道彼此是不是一伙的呢?

答案是:查询

除了代表(根节点),每个人都知道自己的一个朋友,那我们就可以让自己的朋友去问他的朋友,直到自己朋友的朋友...的朋友是这个集合的代表。由于代表(根节点)只有一个,而两人都知道自己和同一个代表是一伙的,那显然两人也是一伙的。这也就是查询的逻辑。

  • 例1:D和C他们是一伙的吗?我们不知道,于是D问他的朋友B,B不是代表,B又去问他的朋友A,A是代表,好了,不用问了;C问他的朋友A,A是代表,不用问了。最终的代表都是A,显然,D和C是一伙的。
  • 例2:E和H他们是一伙的吗?我们也不知道,于是E问他的朋友B,B不是代表,B又去问他的朋友A,A是代表,不用再问了;H问他的朋友G,G不是代表,G问他的朋友F,F是代表,不用再问了。显然,最后的代表不一样,他们不是一伙的。

现在又有个问题,E和H原本不是一伙的,但现在他们交朋友了,根据自己朋友的朋友都是朋友的逻辑,那显然上面两伙人就是一伙人了。这时,他们只要选择一个代表作为他们整体的代表就行了。这种情况叫什么呢?

答案是:合并(两集合合并为一个集合)

他们合并方式可以有很多种,比如让第二伙人的代表F与E交朋友(E作为F的父节点),也可以让F直接和第一伙的代表A交朋友(A作为F的父节点)。

他们怎么交朋友,当然无所谓。但它作为数据结构,会影响到我们算法(特别是查询)的效率,所以怎么交朋友其实是有考究的。显然第二种就比第一种好。因为用第二种方法,H、G、F等找到所在集合代表的速度就更快。

在实现的过程中,我们就希望提高算法的效率,所以就需要路径压缩

3.代码实现

在讲清楚逻辑后,我们就需要看看代码怎么实现了。并查集虽然是树形结构,但他只用数组就可以实现。

3.1创立集合

现在,假设使用一个足够长的数组来存储树结点,那么创立集合就是构造如下森林,其中每个元素都是一个单元素集合,即父节点为自身。

void makeSet(int size)
{for (int i = 1; i <= size; i++){//各自为各自的代表uset[i] = i;}
}

 即:uset[1]=1,uset[2]=2,uset[3]=3,uset[4]=4,uset[5]=5,uset[6]=6。这意味着,uset[i]的朋友(父节点)是i自己。

3.2合并

例如,合并(1,2,3,4),合并(5,6),分别以1和5作为代表

void unite(int x, int y) 
{//先找相对应的代表int x = find(x);int y = find(y);if (x == y){return;}uset[x] = y;
}
  1.  合并2和1:分别找到自己的代表,都是自己2和1,显然2!=1,则uset[2]=1,这意味着,uset[2]的朋友是1;
  2. 合并3和2:分别找到自己的代表,都是自己3和2,显然3!=2,则uset[3]=2,这意味着3的朋友是2;
  3. 合并4和3:分别找到自己的代表,都是自己4和3,显然4!=3,则uset[4]=3,这意味着4的朋友是3;

这样(1,2,3,4)合并完成,此时可以发现它是一棵斜树

同理,合并(5,6)就不再赘述。

现在,我们假设再合并(4,6)。

合并6和4:分别找到自己的代表5和1,显然5!=1,则uset[5]=1,这意味着5的朋友是3(1作为5的父节点)。

当然,以上既可以是uset[x]=y,也可以是uset[y]=x。只要他们每次坚持一样的规则就行了,我们并不关注谁是父结点,谁是最后的根结点(代表)。 

3.3查询

在上面的合并代码中,出现了find(x),他其实就是查找x的代表是谁。

查询采用递归的思想,直到代表是自己(访问到根结点),否则就一直找朋友的朋友...find(uset[i])

int find(int i)
{if (i == uset[i]){return i;}return find(uset[i]);
}

3.4路径压缩

在3.2中,如果采用此方法,最后就会成为一棵斜树。那么在查询时所耗费的时间就变多了,那有没有办法减少所用时间呢?

第一个方法:查找时优化

在每次查找时,令查找路径上的每个结点都指向根结点。

 原先,4找代表1需要3次,现在只需要1次啦!

int find(int i)
{if (i == uset[i]){return i;}return uset[i] = find(uset[i]);//在第一次查找的时候 将结点直接连到跟
}
  • 4找代表:代表不是自己,就一直找到根结点,并将根结点1作为4的父节点(直接朋友)
  • 3找代表:代表不是自己,就一直找到根结点,并将根结点1作为3的父节点(直接朋友)
  • 2找代表:代表是根结点了,不用改变
  • 1找代表:代表是根结点了,不用改变

第二个方法:合并时优化(加权标记法)

我们回忆一下3.2中的合并,无论如何,我们都坚持着一个原则:uset[x]=y,即始终是前面那个数x作为孩子结点,后面那个数y作为x的父节点,最后合并成为了一棵斜树。

那如果我们加入一个判断呢?

如果x作为父节点更好,就让x当父亲,否则,就让y当父亲(结点)。

判断的依据是什么呢?

此时,我们要给每个结点加一个属性,俗称权值,即此结点的高度。

例如合并x和y

  • 若R[x]>R[y],则uset[y]=x;
  • 若R[x]<R[y],则uset[x]=y;
  • 若R[x]==R[y],则都可以。

当合并1和5时,由于R[1]>R[5],则uset[5]=1,让5的父节点为1。否则,就会让树的高度增加,查询效率就降低了。

根据以上信息可以得到:让权值更大的结点作父亲

void unite(int x, int y)
{//先找相对应的代表int x = find(x);int y = find(y);if (x == y){return;}//判断两棵树的高度 在决定谁为子树if (rank[x] < rank[y]){uset[x] = y;}else{uset[y] = x;if (rank[x] == rank[y]){rank[x]++;}}

当然,如果两结点权值相同,谁做父节点都可以,但此时,作父节点的那个结点权值就要增加1。

4.完整代码

这里只给出了模板,就没有写主函数啦。做题时只需套用模板即可。

4.1优化前

#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100int uset[MAXSIZE];//定义一个足够长的数组
//用下标来表示结点
/*
构造并查集
int size 有多少个节点
*/
void makeSet(int size)
{for (int i = 1; i < size; i++){//各自为各自的代表uset[i] = i;}
}/*
找到元素所在的集合的代表 如果位于同一集合 
*/
int find(int i)
{if (i == uset[i]){return i;}return find(uset[i]);
}void unite(int x, int y) 
{//先找相对应的代表int x = find(x);int y = find(y);if (x == y){return;}uset[x] = y;}

 4.2优化后

#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
/*
因为在特殊情况下 这棵树可能是一个长长的树链 设链的最后一个节点为x
每次执行find 相当于遍历整个链条
只需要把便利过的结点都改成跟的子节点 查询就会快很多
*/int uset[MAXSIZE];//定义一个足够长的数组 每个结点
int rank[MAXSIZE];//树的高度//用下标来表示结点
/*
构造并查集
int size 有多少个节点
*/
void makeSet(int size)
{for (int i = 1; i <= size; i++){//各自为各自的代表uset[i] = i;//树的高度rank[i] = 0;}
}/*
找到元素所在的集合的代表 如果位于同一集合
查找元素在哪个集合
*/
int find(int i)
{if (i == uset[i]){return i;}return uset[i] = find(uset[i]);//在第一次查找的时候 将结点直接连到跟
}void unite(int x, int y)
{//先找相对应的代表int x = find(x);int y = find(y);if (x == y){return;}//判断两棵树的高度 在决定谁为子树if (rank[x] < rank[y]){uset[x] = y;}else{uset[y] = x;if (rank[x] == rank[y]){rank[x]++;}}
}

http://chatgpt.dhexx.cn/article/4TRRYduH.shtml

相关文章

时间序列预测的一般步骤

一、ARIMA模型概述 ​​ ARMA模型就是AR和MA的简单结合&#xff0c;同时包含了历史数值项和错误项。由于AR和MA模型都对时间序列有平稳性要求&#xff0c;ARMA模型也存在这个限制&#xff0c;因此我们将其拓展到ARIMA模型&#xff0c;其可以解决非平稳性问题。引入的差分概念是…

时间序列预测算法总结

时间序列算法 time series data mining 主要包括decompose&#xff08;分析数据的各个成分&#xff0c;例如趋势&#xff0c;周期性&#xff09;&#xff0c;prediction&#xff08;预测未来的值&#xff09;&#xff0c;classification&#xff08;对有序数据序列的feature提…

基于深度学习的时间序列预测

# 技术黑板报 # 第十一期 推荐阅读时长&#xff1a;15min 前言 时间序列建模历来是学术和工业界的关键领域&#xff0c;比如用于气候建模、生物科学和医学等主题应用&#xff0c;零售业的商业决策和金融等。虽然传统的统计方法侧重于从领域专业知识层面提供参数模型&#xff0…

预测类问题中的时间序列预测模型

前言&#xff1a;时间序列预测模型适用于含有时间变量的模型预测&#xff0c;对中短期的预测有着较好的结果&#xff0c;对长期预测不适用。本文重点介绍ARIMA模型的原理及代码实现。 其他模型总结&#xff1a;​【Python】Python中的经典时间序列预测模型总结_风度78的博客-C…

Transformer 在时间序列预测中的应用

2017年&#xff0c;Google的一篇 Attention Is All You Need 为我们带来了Transformer&#xff0c;其在NLP领域的重大成功展示了它对时序数据的强大建模能力&#xff0c;自然有人想要把Transformer应用到时序数据预测上。在Transformer的基础上构建时序预测能力可以突破以往的诸…

【推荐收藏】11种比较常用的时间序列预测模型

时间序列及其预测是日常工作中建模&#xff0c;分析&#xff0c;预测的重要组成部分。 本文我们将从0开始介绍时间序列的含义&#xff0c;模型及其分析及其常用的预测模型。 文章目录 交流时间序列定义时间序列预测模型与方法原始数据朴素法简单平均法移动平均法指数平滑法一次…

时间序列预测的20个基本概念总结

1、时间序列 时间序列是一组按时间顺序排列的数据点 比如&#xff1a; 每小时的气压每年的医院急诊按分钟计算的股票价格 2、时间序列的组成部分 时间序列数据有三个主要组成部分。 趋势季节性残差或白噪声 3、趋势 在时间序列中记录的长期缓慢变化/方向。 4、季节性 …

时间序列-预测(Forcasting):时间序列预测算法总结

一、背景介绍 绝大部分行业场景,尤其是互联网、量化行业,每天都会产生大量的数据。金融领域股票价格随时间的走势;电商行业每日的销售额;旅游行业随着节假日周期变化的机票酒店价格等; 我们称这种不同时间收到的,描述一个或多种特征随着时间发生变化的数据,为时间序列…

时间序列预测 | ARMA应用指南

ARMA可谓是时间序列最为经典常用的预测方法&#xff0c;广泛应有于涉及时间序列的各个领域。ARMA模型自出道以来&#xff0c;出场次数不可胜数。想必大家也都不陌生&#xff0c;常学常新&#xff0c;我们今天不妨再来回顾一遍&#xff5e;。 ARMA全称Autoregressive moving ave…

时间序列预测的7种方法

import pandas as pd#取数 #dfpd.read_csv(jetrail.csv) #print(df.head()) ID Datetime Count 0 0 25-08-2012 00:00 8 1 1 25-08-2012 01:00 2 2 2 25-08-2012 02:00 6 3 3 25-08-2012 03:00 2 4 4 25-08-2012 04:00 2#pr…

Transformer时间序列预测

介绍&#xff1a; 提示&#xff1a;Transformer-decoder 总体介绍 本文将介绍一个 Transformer-decoder 架构&#xff0c;用于预测Woodsense提供的湿度时间序列数据集。该项目是先前项目的后续项目&#xff0c;该项目涉及在同一数据集上训练一个简单的 LSTM。人们认为 LSTM 在…

时间序列预测的8种常用方法简介

时间序列预测的7种方法 1. 朴素预测法&#xff08;Naive Forecast) 如果数据集在一段时间内都很稳定&#xff0c;我们想预测第二天的价格&#xff0c;可以取前面一天的价格&#xff0c;预测第二天的值。这种假设第一个预测点和上一个观察点相等的预测方法就叫朴素法&#xff…

常见的时间序列预测模型python实战汇总

最完整的时间序列分析和预测&#xff08;含实例及代码&#xff09;&#xff1a;https://mp.weixin.qq.com/s/D7v7tfSGnoAqJNvfqGpTQA 1 时间序列与时间序列分析 在生产和科学研究中&#xff0c;对某一个或者一组变量 x(t)x(t) ARIMA 模型对时间序列的要求是平稳型。因此&#…

时间序列预测模型

时间序列数据一般有以下几种特点&#xff1a;1.趋势(Trend) 2. 季节性(Seasonality)。 趋势描述的是时间序列的整体走势&#xff0c;比如总体上升或者总体下降。下图所示的时间序列是总体上升的&#xff1a; 季节性描述的是数据的周期性波动&#xff0c;比如以年或者周为周期&…

【数据分析】基于时间序列的预测方法

时间序列预测 目录 时间序列预测1.时间序列介绍2.原始数据集3.导入数据4.检测时间序列的平稳性5.如何使时间序列平稳5.1 估计和消除趋势5.1.1 对数转换5.1.2 移动平均 5.2 消除趋势和季节性5.2.1 差异化5.2.2 分解 6.预测时间序列6.1 AR Model6.2 MA Model6.3 Combined Model6.…

Matlab实现时间序列预测

文章目录 一、数据准备二、时间序列预测分类1、输入为xt&#xff0c;输出是yt2、有x值&#xff0c;有y值&#xff1a;NARX(1)选择模型类型(2)选择输出&#xff0c;只有y_t(3)选择70%用来作为训练数据&#xff0c;15%用来作为验证使用&#xff0c;15%用来测试(4)选择delay(5)开始…

【时序预测】Transformer模型在时间序列预测领域的应用

今天又是一篇Transformer梳理文章&#xff0c;这次应用场景是时间序列预测。Transformer的序列建模能力&#xff0c;让其天然就比较适合时间序列这种也是序列类型的数据结构。但是&#xff0c;时间序列相比文本序列也有很多特点&#xff0c;例如时间序列具有自相关性或周期性、…

11种常见的时间序列预测方法

参考内容&#xff1a;4大类11种常见的时间序列预测方法总结和代码示例 代码地址&#xff1a; https://github.com/SeafyLiang/machine_learning_study/blob/master/time_series 11种常见的时间序列预测方法 1、指数平滑Exponential Smoothing2、Holt-Winters 法3、自回归 (AR)…

时间序列预测方法最全总结!

时间序列预测就是利用过去一段时间的数据来预测未来一段时间内的信息&#xff0c;包括连续型预测&#xff08;数值预测&#xff0c;范围估计&#xff09;与离散型预测&#xff08;事件预测&#xff09;等&#xff0c;具有非常高的商业价值。 需要明确一点的是&#xff0c;与回归…

时间序列(一):时间序列数据与时间序列预测模型

时间序列系列文章&#xff1a; 时间序列&#xff08;一&#xff09;&#xff1a;时间序列数据与时间序列预测模型 时间序列&#xff08;二&#xff09;&#xff1a;时间序列平稳性检测 时间序列&#xff08;三&#xff09;&#xff1a;ARIMA模型实战 时间序列及其预测是日常工…