数据结构——并查集(C代码实现)

结构定义

  并查集本质上是基于染色的思想,将属于相同集合内的元素染成相同的颜色;一开始所有元素的颜色是不同的,通过合并两个元素的操作,将其中一个元素的颜色染成另一个元素的颜色。

  简单地,我们可以使用数组来存元素的颜色状态,因为使用了数组,所以还需要记录数组的大小。


结构操作

  1. 初始化并查集init()。创建并初始化并查集的存储空间。
  2. 获取元素的颜色find()。返回当前元素的颜色。
  3. 合并两个元素merge()。将其中一个元素的颜色染成另一个元素的颜色。
  4. 清理并查集空间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 ;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值