并查集理论以及实现

一, 并查集理解

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值