数据结构 - 森林与并查集

使用场景

并查集常用于操作不相交集合,判断或操作相关联关系,类似将森林中的两棵树进行合并

C 实现

quick_find 算法,将同属于一区块的集合进行 “染色” 判断

#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 <= n; ++i) {
		u->color[i] = i;
	}
	return u;
}

// O(1)
int find(UnionSet *u, int x) {
	return u->color[x];
}

// 将颜色 a 染成颜色 b, O(n)
int merge(UnionSet *u, int a, int b) {
	if (find(u, a) == find(u, b)) return 0;
	int color_a = u->color[a];
	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;
}

int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	UnionSet *u = init(n);
	// 简单演示查询和合并
	for (int i = 0; i < m; ++i) {
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		switch (a) {
			case 1: merge(u, b, c); break;
            case 2: printf("%s\n", find(u, b) == find(u, c) ? "Yes" : "No"); break;
		}
	}
	clear(u);
	return 0;
}

union_set 算法,链表合并,并通过路径压缩进行优化

#include <stdio.h>
#include <stdlib.h>

#define swap(a, b) { \
    __typeof(a) __temp = b; \
    b = a; a = __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;
}

// 一般都是 O(1), 出现路径压缩的情况会有些损失
int find(UnionSet *u, int x) {
	return u->father[x] = (u->father[x] == x ? x : find(u, u->father[x]));
}

// 一般都是 O(1),在 find 基础上进行合并
int merge(UnionSet *u, int a, int b) {
	int fa = find(u, a), fb = find(u, b);
	if (fa == fb) return 0;
	// 合并时优先把节点少的合并到节点多的
	// if (u->size[fa] < u->size[fb]) swap(fa, fb);
	u->father[fb] = fa;
	// u->size[fa] += u->size[fb];
	return 1;
}

void clear(UnionSet *u) {
	if (u == NULL) return;
	free(u->father);
	// free(u->size);
	free(u);
	return;
}

int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	UnionSet *u = init(n);
	// 简单演示查询和合并
	for (int i = 0; i < m; ++i) {
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		switch (a) {
			case 1: merge(u, b, c); break;
            case 2: printf("%s\n", find(u, b) == find(u, c) ? "Yes" : "No"); break;
		}
	}
	clear(u);
	return 0;
}

C++ 实现

#include <bits/stdc++.h>

using namespace std;

class DisjointSet {
private:
	// rank 节点数量来判断谁对谁进行合并更佳,路径更短
    int *father, *rank;
public:
    DisjointSet(int size) {
        father = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; ++i) {
            father[i] = i;
            rank[i] = 0;
        }
    }
    ~DisjointSet() {
        delete[] father;
        delete[] rank;
    }
    int find_set(int node) {
		if (father[node] != node) {
			// 路径优化
			father[node] = find_set(father[node]);
			rank[node] = rank[father[node]] + 1;
		}
		return father[node];
	}
	bool merge(int node1, int node2) {
		int ancestor1 = find_set(node1);
        int ancestor2 = find_set(node2);
		if (ancestor1 != ancestor2) {
			// 谁节点数量多优先选谁做父节点
            if (rank[ancestor1] > rank[ancestor2]) {
                swap(ancestor1, ancestor2);
            }
            father[ancestor1] = ancestor2;
            rank[ancestor2] = max(rank[ancestor2], rank[ancestor1] + 1);
            return true;
        }
        return false;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值