并查集理论以及实现
一, 并查集理解
1.并查集-部落
简单说是一个只用于不交集的合并、查找,其他的操作没啥意义,看似和树没啥关系,实际操作中我们会用树来进行一个结构优化。
并查集可以理解为一个部落,部落的每个元素是个村庄,村庄之间有的可以互通,所有互通的村庄的集合就是一个部落,而每个部落都会有个代表村庄,也可以说是老大。
2.并查集操作
a.查找,查找元素是属于哪个集合,通常返回这个集合的代表元素,其目的是为了判断两个元素是否同属于一个集合
b.合并,将两个集合合并为一个集合
3.并查集两种实现思路
a.Quick find
如果只进行一次合并,后面都是查找,就使用这种
查找效率:O(1) 组合效率:O(N)
每一个元素,可以有个标号,代表这个元素的老大,当改变这个元素的老大,则这个元素的小弟也会认这个元素的老大做大哥
如图例:初始化时每个元素的老大是自己,将0、4合并后0认4做老大,0元素下标改成4
再将4、2合并后4认2做老大,以及4元素的小弟也要认2做老大,也就是把所有下标是4的都改成2
b.Quick union
适合组合频率高,查找频率低
查找效率:O(logN) 组合效率:O(logN)
看到时间复杂度为logN的结构应该对树要敏感,而Quickunion的核心就是树结构,当初始化时元素指向自己
假如我们将0、1组合此时0指向1,再将2、0组合,此时2指向0。
如果我们要查找2所在集合需要从2找到0再从0找到1,查找的集合就是他们的根,查找效率为整棵树的高度logN
如果我们要合并3和2,合并的其实是他们所在集合的根,也就是把4的下标改为1,合并他们根的过程就是从2找到1,然后从3找到4,最后将4和1合并
注意我们代码实现还需要一张size表,用来存储对应树的节点个数,初始化时每个节点的size为1
如果合并0、1,此时0的parentID为1,而1的是根节点就更新1的size为2
当我们把2、4合并,4的size变为2
再把4、3合并,3的size就为3
最后把1、3合并,3的size就为5
二、QuickFind代码实现
1.头文件中的接口
//
// Created by 27893 on 2025/7/12.
//#pragma once
typedef int Element_t;
//
// Created by 27893 on 2025/7/12.
//#pragma once
#include "UnionFindSet.h"
typedef struct {int n;Element_t*data;int*groupID;
}QFSet_t;QFSet_t*createQFSet(int n);
int initQFSet(QFSet_t*set,Element_t*data,int n);
int releaseQFSet(QFSet_t*set);int unionQFSet(QFSet_t*set,Element_t a,Element_t b);int isSameQFSet(QFSet_t*set,Element_t a,Element_t b);
2.将头文件中的接口一一实现
//
// Created by 27893 on 2025/7/12.
//
#include <string.h>
#include <stdlib.h>
#include "QuickFindSet.h"#include <stdio.h>QFSet_t * createQFSet(int n) {QFSet_t*set=malloc(sizeof(QFSet_t));if(set==NULL)return NULL;set->n=n;set->data=malloc(sizeof(Element_t)*n);set->groupID=malloc(sizeof(int)*n);if(set->data==NULL||set->groupID==NULL) {free(set->data);return NULL;}memset(set->data,0,sizeof(Element_t)*n);memset(set->groupID,0,sizeof(int)*n);return set;
}int initQFSet(QFSet_t *set, Element_t *data, int n) {if(set==NULL||data==NULL)return 0;n=set->n<n?set->n:n;for (int i=0;i<n;i++) {set->data[i]=data[i];set->groupID[i]=i;}return 1;
}int releaseQFSet(QFSet_t *set) {if(set==NULL)return 0;free(set->data);free(set->groupID);free(set);return 1;
}static int findIndex(const QFSet_t*set,Element_t data) {for (int i=0;i<set->n;i++) {if (set->data[i]==data)return i;}return -1;
}int isSameQFSet(QFSet_t *set, Element_t a, Element_t b) {//找到a和b的索引int aindex=findIndex(set,a);int bindex=findIndex(set,b);if(aindex==-1||bindex==-1)return 0;//比较这两个值在groupID中是否相等return set->groupID[aindex]==set->groupID[bindex];
}int unionQFSet(QFSet_t *set, Element_t a, Element_t b) {//1.找到a和b的索引,比较这两个值在groupID中是否相等int aindex=findIndex(set,a);int bindex=findIndex(set,b);if(aindex==-1||bindex==-1)return 0;//2.不相等就把和a的groupID相同的元素的groupID改成b的groupIDint groupID=set->groupID[aindex];for (int i=0;i<set->n;i++) {if (set->groupID[i]==groupID) {set->groupID[i]=set->groupID[bindex];}}printf("union %d and %d\n",a,b);return 1;
}
3.测试代码是否有bug
按照左图中的结构进行测试,或者自己模拟数据进行测试
//
// Created by 27893 on 2025/7/12.
//
#include <stdio.h>
#include "QuickFindSet.h"
void test() {int n=9;QFSet_t*set=createQFSet(n);Element_t data[]={0,1,2,3,4,5,6,7,8};initQFSet(set,data,n);unionQFSet(set,3,4);unionQFSet(set,8,0);unionQFSet(set,2,3);unionQFSet(set,5,6);if (isSameQFSet(set,0,2)) {printf("yes\n");}else {printf("no\n");}if (isSameQFSet(set,2,4)) {printf("yes\n");}else {printf("no\n");}unionQFSet(set,5,1);unionQFSet(set,7,3);unionQFSet(set,1,6);unionQFSet(set,4,8);if (isSameQFSet(set,0,2)) {printf("yes\n");}else {printf("no\n");}if (isSameQFSet(set,2,4)) {printf("yes\n");}else {printf("no\n");}releaseQFSet(set);
}
int main() {test();return 0;
}
明天我们再把QuickUnion实现