一, 并查集理解
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的groupID
int 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实现
三,QuickUnion代码实现
1.头文件中的接口
//
// Created by 27893 on 2025/7/12.
//
#pragma once
typedef int Element_t;
//
// Created by 27893 on 2025/7/13.
//
#pragma once
#include "UnionFindSet.h"
typedef struct {
Element_t* data;
int n;
int*parentID;
int*size;
}QUSet_t;
QUSet_t*createQUSet(int n);
void initQUSet(QUSet_t*set,int*data,int n);
void releaseQUSet(QUSet_t*set);
int isSameQUSet(QUSet_t*set,Element_t a,Element_t b);
void unionQUSet(QUSet_t*set,Element_t a,Element_t b);
2.将头文件中的接口一一实现
//
// Created by 27893 on 2025/7/13.
//
#include "QuickUnionSet.h"
#include <stdio.h>
#include <stdlib.h>
QUSet_t * createQUSet(int n) {
QUSet_t*set=malloc(sizeof(QUSet_t));
if (set==NULL) {
printf("malloc failed");
return NULL;
}
set->n=n;
set->data=malloc(sizeof(Element_t)*n);
set->parentID=malloc(sizeof(int)*n);
set->size=malloc(sizeof(int)*n);
if (set->data==NULL||set->parentID==NULL||set->size==NULL) {
printf("malloc failed");
free(set->data);
free(set->parentID);
free(set->size);
free(set);
return NULL;
}
return set;
}
void initQUSet(QUSet_t *set, int *data, int n) {
n=n<set->n?n:set->n;
for (int i=0;i<n;i++) {
set->data[i]=data[i];
set->parentID[i]=i;
set->size[i]=1;
}
}
void releaseQUSet(QUSet_t *set) {
free(set->data);
free(set->parentID);
free(set->size);
free(set);
}
static int findIndex(const QUSet_t*set,Element_t e) {
for (int i=0;i<set->n;i++) {
if (set->data[i]==e) {
return i;
}
}
return -1;
}
static int findRoot(QUSet_t*set,int i) {
while (set->parentID[i]!=i) {
i=set->parentID[i];
}
return i;
}
int isSameQUSet(QUSet_t *set, Element_t a, Element_t b) {
//找到a、b的索引
int aIndex=findIndex(set,a);
int bIndex=findIndex(set,b);
//通过索引找到a、b的根节点
if (aIndex==-1||bIndex==-1) {
return -1;
}
int aroot=findRoot(set,aIndex);
int broot=findRoot(set,bIndex);
//判断根节点是否相同
return aroot==broot;
}
void unionQUSet(QUSet_t *set, Element_t a, Element_t b) {
//找到a、b的索引
int aIndex=findIndex(set,a);
int bIndex=findIndex(set,b);
//通过索引找到a、b的根节点
if (aIndex==-1||bIndex==-1) {
return;
}
int aroot=findRoot(set,aIndex);
int broot=findRoot(set,bIndex);
//判断a,b哪个树的节点更少,把少的树的根节点指向多的树的根节点
if (set->size[aroot]<set->size[broot]) {
//修改被合并树的size
set->parentID[aroot]=broot;
set->size[broot]+=set->size[aroot];
}else {
set->parentID[broot]=aroot;
set->size[aroot]+=set->size[broot];
}
}
3.测试代码是否有bug
//
// Created by 27893 on 2025/7/12.
//
#include <stdio.h>
#include "QuickUnionSet.h"
void test2() {
int n=9;
QUSet_t*set=createQUSet(n);
Element_t data[]={0,1,2,3,4,5,6,7,8};
initQUSet(set,data,n);
unionQUSet(set,3,4);
unionQUSet(set,8,0);
unionQUSet(set,2,3);
unionQUSet(set,5,6);
if (isSameQUSet(set,0,2)) {
printf("yes\n");
}else {
printf("no\n");
}
if (isSameQUSet(set,2,4)) {
printf("yes\n");
}else {
printf("no\n");
}
unionQUSet(set,5,1);
unionQUSet(set,7,3);
unionQUSet(set,1,6);
unionQUSet(set,4,8);
if (isSameQUSet(set,0,2)) {
printf("yes\n");
}else {
printf("no\n");
}
if (isSameQUSet(set,2,4)) {
printf("yes\n");
}else {
printf("no\n");
}
releaseQUSet(set);
}
int main() {
test2();
return 0;
}