数据结构——并查集(C代码实现)
目录
- 结构定义
- 结构操作
- 不同的算法实现
- Quick-Find算法
- Quick-Union算法
结构定义
并查集本质上是基于染色的思想,将属于相同集合内的元素染成相同的颜色;一开始所有元素的颜色是不同的,通过合并两个元素的操作,将其中一个元素的颜色染成另一个元素的颜色。
简单地,我们可以使用数组来存元素的颜色状态,因为使用了数组,所以还需要记录数组的大小。
结构操作
- 初始化并查集
init()
。创建并初始化并查集的存储空间。 - 获取元素的颜色
find()
。返回当前元素的颜色。 - 合并两个元素
merge()
。将其中一个元素的颜色染成另一个元素的颜色。 - 清理并查集空间
clear()
。
不同的算法实现
Quick-Find算法
Quick-Find算法的合并操作,是将与其中一个元素在同一个集合中的所有元素的颜色,染成另一个元素的颜色。
联通判断:时间复杂度O(1)
;
合并操作:时间复杂度O(n)
。
代码实现:
#include <stdio.h>
#include <stdlib.h>
typedef struct UnionSet {
//定义颜色数组
int *color;
//定义并查集的大小
int n;
} UnionSet;
//初始化并查集
UnionSet *init(int n) {
UnionSet *u = (UnionSet *)malloc(sizeof(UnionSet));
u->color = (int *)malloc(sizeof(int) * (n + 1));
u->n = n;
//颜色数组初始状态颜色都是元素本身的值
for (int i = 1; i <= u->n; i++) {
u->color[i] = i;
}
return u;
}
//获取元素的颜色
int find(UnionSet *u, int x) {
return u->color[x];
}
//合并两个元素
int merge(UnionSet *u, int a, int b) {
//如果颜色相同,则不用合并操作
if (find(u, a) == find(u, b)) return 0;
//定义变量记录元素a的颜色
int color_a = u->color[a];
//并查集中与元素a相同颜色的元素,颜色均染成b的颜色
for (int i = 1; i <= u->n; i++) {
if (u->color[i] != color_a) continue;
u->color[i] = u->color[b];
}
return 1;
}
//清理并查集的空间
void clear(UnionSet *u) {
if (u == NULL) return ;
free(u->color);
free(u);
return ;
}
Quick-Union算法
Quick-Union算法判断两个元素是否为同一集合时,不是判断颜色是否相同,而是判断它们的父亲或者说领导是否相同;Quick-Union算法的合并操作,是将其中一个元素的父亲,替换为另一个元素的父亲。
联通判断:时间复杂度O(logN)
;
合并操作:时间复杂度O(logN)
。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#define swap(a, b) {\
__typeof(a) __temp = a;\
a = b; b = __temp;\
}
//初始化并查集
typedef struct UnionSet {
//定义元素的父亲数组
int *father;
//定义记录某一元素所在集合中共有多少个元素的数组
int *size;
//定义并查集大小
int n;
} UnionSet;
//初始化并查集
UnionSet *init(int n) {
UnionSet *u = (UnionSet *)malloc(sizeof(UnionSet));
u->father = (int *)malloc(sizeof(int) * (n + 1));
u->size = (int *)malloc(sizeof(int) * (n + 1));
u->n = n;
//各元素初始父亲为其本身的值,其所在集合均只有一个元素
for (int i = 1; i <= n; i++) {
u->father[i] = i;
u->size[i] = 1;
}
return u;
}
//获取元素的父亲
int find(UnionSet *u, int x) {
//元素的父亲是其本身值,返回其本身值
//否则递归查找其当前父亲的父亲
if (u->father[x] == x) {
return x;
} else {
return find(u, u->father[x]);
}
}
//合并两个元素
int merge(UnionSet *u, int a, int b) {
//记录元素a和元素b的父亲
int fa = find(u, a), fb = find(u, b);
//如果父亲相同,不用进行合并操作
if (fa == fb) return 0;
//如果父亲不同,且a所在集合元素个数比b所在集合元素个数小,a合并到b的集合中
if (u->size[fb] < u->size[fa]) swap(fa, fb);
u->father[fa] = fb;
u->size[fb] += u->size[fa];
return 1;
}
//清理并查集的空间
void clear(UnionSet *u) {
if (u == NULL) return ;
free(u->father);
free(u->size);
free(u);
return ;
}
PS:Quick-Union算法再增加路径压缩优化一下,将处于同一集合中的所有元素的父亲记录为当前集合的最终父亲或者说是最终领导。
联通判断:时间复杂度接近O(1)
,有明显提高;
合并操作:时间复杂度接近O(1)
,有明显提高。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#define swap(a, b) {\
__typeof(a) __temp = a;\
a = b; b = __temp;\
}
//初始化并查集
typedef struct UnionSet {
//定义元素的父亲数组
int *father;
//定义并查集大小
int n;
} UnionSet;
//初始化并查集
UnionSet *init(int n) {
UnionSet *u = (UnionSet *)malloc(sizeof(UnionSet));
u->father = (int *)malloc(sizeof(int) * (n + 1));
u->n = n;
//各元素初始父亲为其本身的值
for (int i = 1; i <= n; i++) {
u->father[i] = i;
}
return u;
}
//获取元素的父亲
int find(UnionSet *u, int x) {
//元素的父亲是其本身值,返回其本身值
//否则递归查找其当前父亲的父亲,并将当前元素的父亲改为最终父亲,到达压缩路径的目的
if (u->father[x] == x) {
return x;
} else {
return u->father[x] = find(u, u->father[x]);
}
}
//合并两个元素
int merge(UnionSet *u, int a, int b) {
//记录元素a和元素b的父亲
int fa = find(u, a), fb = find(u, b);
//如果父亲相同,不用进行合并操作
if (fa == fb) return 0;
//如果父亲不同,元素a的父亲改为元素b的父亲
u->father[fa] = fb;
return 1;
}
//清理并查集的空间
void clear(UnionSet *u) {
if (u == NULL) return ;
free(u->father);
free(u);
return ;
}
还没有评论,来说两句吧...