并查集与最小生成树 模板

1.并查集
"在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(union-find algorithm)定义了两个用于此数据结构的操作:
Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(union-find data structure)或合并-查找集合(merge-find set)。其他的重要方法,MakeSet,用于创建单元素集合。有了这些方法,许多经典的划分问题可以被解决。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。"
--------维基百科

1.初始化每个门派的代表元素

void init()//初始化每一个集合的门派代表
{
	for (int i = 1; i <= N; i++)
		father[i] = i;
}

2 找到每个元素所属的门派

int find(int x)//找门派代表
{
	while (father[x] != x) x = father[x];
	//if(father[x]!=x) father[x]=find(father[x]);//递归写法
	return x;
}

3 认主归宗,找到组织


void merge(int x, int y)
{
	//cout << "merge " << find(x) << " " << find(y) << endl;//before merge 你可以看看
	int a, b;
	a = find(x), b = find(y);//找到x的门派,再找到y的门派
	//if (a == b)//本是同一家的就不用合并,所以略去
		//return;
	if(a!=b)
		father[a] = b;//不是的话,就把y归为x门下
	//cout << "merge " << find(x) << " " << find(y) << endl;//after merge
}

来一道开胃蔡:P3367 【模板】并查集
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式
第一行包含两个整数N、M,表示共有N个元素和M个操作。

接下来M行,每行包含三个整数Zi、Xi、Yi

当Zi=1时,将Xi与Yi所在的集合合并

当Zi=2时,输出Xi与Yi是否在同一集合内,是的话输出Y;否则话输出N

输出格式
如上,对于每一个Zi=2的操作,都有一行输出,每行包含一个大写字母,为Y或者N

输入输出样例
输入 #1 复制
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4
输出 #1 复制
N
Y
N
Y
说明/提示
时空限制:1000ms,128M

数据规模:

对于30%的数据,N<=10,M<=20;

对于70%的数据,N<=100,M<=1000;

对于100%的数据,N<=10000,M<=200000。

#include <iostream>

using namespace std;
const int N = 200005;
int father[N];

void init()//初始化每一个集合的门派代表
{
	for (int i = 1; i <= N; i++)
		father[i] = i;
}

int find(int x)//找门派代表
{
	while (father[x] != x) x = father[x];
	return x;
}

void merge(int x, int y)
{
	//cout << "merge " << find(x) << " " << find(y) << endl;//before merge 
	int a, b;
	a = find(x), b = find(y);//找到x的门派,再找到y的门派
	//if (a == b)//本是同一家的就不用合并
		//return;
	if(a!=b)
		father[a] = b;//不是的话,就把y归为x门下
	//cout << "merge " << find(x) << " " << find(y) << endl;//after merge
}
int main()
{
	int n, m;
	cin >> n >> m;
	int x, y, z;
	init();
	for (int i = 0; i < m; i++)
	{
		cin >> z >> x >> y;
		if (z & 1)
			merge(x, y);
		else
			if (find(x) == find(y))
				cout << "Y\n";
			else
				cout << "N\n";
	}
	

	system("pause");
	return 0;
}

基本的差不多就这样。
如果想深入了解学习 可以看看大佬的博客 并查集(Union-Find Algorithm)
2.最小生成树
最小生成树是一副连通加权无向图中一棵权值最小的生成树。
维基百科
最小生成树其实是最小权重生成树的简称。

假设 nn 表示图中点数,mm 表示图中边数。

Prim算法
适用于稠密图,时间复杂度 O(n2)O(n2)。

核心思想:每次挑一条与当前集合相连的最短边。

C++ 代码
// st[i] 表示点i是否在当前生成树集合中
// dist[i] 表示点i到当前集合的最短边的长度
// g[i][j] 表示点i和点j之间边的长度
// 返回值:最小生成树中所有边的总长度

int Prim()
{
    int res = 0;
    for (int i = 1; i <= n; i ++ )
    {
        dist[i] = INF;
        st[i] = false;
    }
    dist[1] = 0;
    for (int i = 1; i <= n; i ++ )
    {
        int id = -1, min_dist = INF;
        // 寻找最短边
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && dist[j] < min_dist)
            {
                id = j;
                min_dist = dist[j];
            }
        st[id] = true;
        res += dist[id];
        // 用新加入的点更新其余点到生成树的最短边
        for (int j = 1; j <= n; j ++ )
            if (!st[j])
                dist[j] = min(dist[j], g[id][j]);
    }
    return res;
}

Kruskal算法
适用于稀疏图,时间复杂度 O(mlogm)O(mlogm)。

核心思想:从小到大挑不多余的边。

C++ 代码

// 边的信息
struct Edge
{
    int a, b, v;
    bool operator< (const Edge &W) const
    {
        return v < W.v;
    }
};

// 并查集——寻找当前集合的代表元素
int find(int x)
{
    if (father[x] != x) father[x] = find(father[x]);
    return father[x];
}

// 所有边存储在 Edge edges[M]; 
// 函数返回最小生成树中所有边的总长度

int Kruskal()
{
    int res = 0;
    // 初始化并查集代表元素
    for (int i = 1; i <= n; i ++ ) father[i] = i;
    sort(edge, edge + m);
    for (int i = 0; i < m; i ++ )
    {
        int a = edge[i].a, b = edge[i].b;
        if (find(a) != find(b))
        {
            res += edge[i].v;
            father[find(a)] = find(b);
        }
    }
    return res;
}

转自YXC大佬模板


这是一道模板题,详情可点击 洛谷 P3366

#include <iostream>
#include <algorithm>
using namespace std;
const int  N = 5001,M = 200005;
int father[N];
int n, m;
struct Edge//图
{
	int x, y, z;
	bool operator <(const Edge &e) const
	{
		return z < e.z;
	}
}E[M];

void init()
{
	for (int i = 0; i < n; i++)
		father[i] = i;
}
int find(int x)
{
	while (father[x] != x) x = father[x];
	return father[x];
}
void merge(int x, int y)
{
	int a = find(x), b = find(y);
	if (a != b)
		father[a] = b;
}
int Kruskal()
{
	init();
	int ans=0;
	sort(E, E + m);
	for (int i = 0; i < m; i++)
	{
		if (find(E[i].x) != find(E[i].y))
		{
			ans += E[i].z;
			merge(E[i].x, E[i].y);
		}
	}
	return ans;
}
int main()
{
	cin >> n >> m;
	for (int i = 0; i < m; i++)
		cin >> E[i].x >> E[i].y >> E[i].z;
	cout << Kruskal();


	system("pause");
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值