最小生成树基础算法模板

终于写完了神奇的深度搜素。。。现在又要开始看一些和树有关的问题了
其它的生成树都还没学,就先写写已经学过的最小生成树好了。。。QwQ 。。。其实我也不确定自己的写法对不对,就只是一些自己的理解,反正用到现在好似没什么大问题吧QwQ
首先,最小生成树的定义,是指在连通网的所有生成树中,所有边的代价和最小的生成树
所以最小生成树,通常会用来做一些找最小价格,找最优路径的问题。我们通常用两个算法可以用来写最小生成树问题,分别是Kruskal算法Prim算法,接下来我们会对两种算法都进行介绍,并且在例题中实际使用它。

1. 两个算法的区别

主要的区别实在时间复杂度上,Prim算法的复杂度为O(n^2),而Kruskal算法的复杂度为O(mlogn)。
从表面上看起来,好似Kruskal的复杂度更低,毕竟它有一个log嘛,从外形上来讲就比平方好看的多,那我们直接学Kruskal不就好了,学Prim干嘛呢?但实际上,在许多为题中,给出的m的数值会非常大,在最坏情况的稠密图中,m的数值可以达到n * (n - 1),这个时候,mlogn的复杂度就不见得比n^2优秀了。所以,一般 情况,在题目给出的图为稀疏图时,我们选用Kruskal,而稠密图时,我们则选择Prim。

2. Kruskal算法

<1> 算法思路

Kruskal算法是一个由快速排序和并查集两种基础算法组合实现的。首先,用结构体记录每条边的连接点和长度,按照长度从小到大进行排序后开始查找,如果这条边连接的两点没有在一个集合中,则在总长度上加上这条边的长度,最终的长度也就是可以把所有点相连的路的最小长度。
这个算法的思路非常简单,也很容易看出时间复杂度,利用并查集(并查集的复杂度是O(1)的)搜素m次边,复杂度为O(m),然后排序其实有很多种选择,但我相信追求完美的孩子一定会选择快排吧,嘿嘿嘿,所以在乘上快排的时间复杂度O(logn),就可以算出整个krulskal算法的复杂度就是O(mlongn)

<2>核心算法模板


int n, m, f[100010];

// 结构体,用于记录边
// a, b为边连接的两个点,len为边的长度
struct edge
{
	int a, b, len;
}edges[10010];

// 按照边权值从小到大排序
bool cmp(edge x, edge y)
{
	return x.len < y.len
}

// 并查集的初始化
void init()
{
	for(int i = 0; i <= n; i ++) f[i] = i;
}

// 并查集的核心算法。。。就不多说了
int found(int x)
{
	if(f[x] != x) f[x] = found(f[x]);
	return f[x];
}

// 检查两点是否已经相连
bool unite(int a, int b)
{
	int x = found(a);
	int y = found(b);
	if(x != y)
	{
		f[y] = x;
		return true;
	}
	return false;
}

// krulkal算法
int krulkal()
{
	sort(edges, edges + n);
	init();
	int res = 0;
	
	for(int i = 0; i < m; i ++)
	{
		if(unite(edges[i].a, edges[i].b))
		{
			res += edges[i].len;
		}
	}
	
	return res;
}

<3> 来到题目体验一下鸭

给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。
给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。
由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。

输入格式

第一行包含两个整数n和m。
接下来m行,每行包含三个整数u,v,w,表示点u和点v之间存在一条权值为w的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。

数据范围

1≤n≤1051≤n≤105,

1≤m≤2∗1051≤m≤2∗105,

图中涉及边的边权的绝对值均不超过1000。

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6

原题链接:Kruskal算法求最小生成树

AC代码

#include
using namespace std;

const int N = 100010;

int f[N];
struct node
{
    int a, b;
    int len;
}p[N * 2];
int n, m;

bool cmp(node x, node y)
{
    return x.len < y.len;
}

void init()
{
    for(int i = 0; i <= n; i ++) f[i] = i; 
}

int found(int x)
{
    if(f[x] != x) f[x] = found(f[x]);
    return f[x];
}

bool unite(int a, int b)
{
    int x = found(a);
    int y = found(b);
    if(x != y)
    {
        f[y] = x;
        return true;
    }
    return false;
}

int main()
{
    scanf("%d%d", &n, &m);
    
    for(int i = 0; i < m; i ++)
    {
        scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].len);
    }
    sort(p, p + m, cmp);
    init();
    int ans = 0, cnt = 0;
    for(int i = 0; i < m; i ++)
    {
        if(unite(p[i].a, p[i].b))
        {
            ans += p[i].len;
            cnt ++;
        }
    }
    if(cnt < n - 1) puts("impossible");
    else cout << ans << endl;
    return 0;
}

3.Prim算法

<1> 算法思路

其实Prim算法的思路和最短路的Dijkstra算法蛮像的,不同点是每次更新的不是某个点到另一个点的最短距离,而是更新已经成为一个集合的所有点到剩下的点的最短距离(也就是说集合到点的最短距离)。
所以有好好理解Dijkstra算法的小可爱一定可以很轻松的康懂Prim吧,我就不多解释咯。

<2> 核心算法模板


int dist[100010], g[110][110];
bool st[100010];
int n, m;

int prim()
{
	int res = 0;
	
	memset(dist, 0x3f, sizeof dist);
	memset(st, false, sizeof st);
	
	for(int i = 0; i < n; i ++)
	{
		int t = -1;
		for(int j = 1; j <= n; j ++)
		{
			if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
		}
		if(i && t == -1) return -1;
		else if(i) res += dist[t];
		st[t] = true;
		
		for(int j = 1; j <= n; j ++)
		{
			dist[j] = min(dist[j], g[t][j]);
		}
	}
	
	return res;
}

<3> 来到题目体验一下鸭

给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。
给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。
由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。

输入格式

第一行包含两个整数n和m。
接下来m行,每行包含三个整数u,v,w,表示点u和点v之间存在一条权值为w的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。

数据范围

1≤n≤5001≤n≤500,

1≤m≤1051≤m≤105,

图中涉及边的边权的绝对值均不超过10000。

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6

原题链接:Prim算法求最小生成树

AC代码

#include
using namespace std;

const int M = 100010, N = 510;
const int INF = 0x3f3f3f3f;
int g[N][N], dist[M];
int n, m, a, b, c;
bool vis[M];

int prim()
{
    int res = 0;
    memset(dist, 0x3f, sizeof dist);
    for(int i = 0; i < n; i ++)
    {
        int t = - 1;
        for(int j = 1; j <= n; j ++)
        {
            if(!vis[j] && (t == -1 || dist[t] > dist[j])) t = j;
        }
        if(i && dist[t] == INF) return INF;
        if(i) res += dist[t];
        vis[t] = true;
        for(int j = 1; j <= n; j ++)
        {
            dist[j] = min(dist[j], g[t][j]);
        }
    }
    return res;
}

int main()
{
    cin >> n >> m;
    memset(g, 0x3f, sizeof g);
    for(int i = 0; i < m; i ++)
    {
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    int ans = prim();
    if(ans == INF) puts("impossible");
    else cout << ans << endl;
    return 0;
}

好啦~到这里就结束啦,嘿嘿。
相信有好好理解最短路的小可爱萌学起来应该还是不难的,毕竟思路有很多相像的地方,不过要熟练掌控还是得多刷题鸭~
然后嘛。。。如果还没学最短路的小可爱,可以点点下面的链接参考一下QwQ
最短路基础算法模板大全~~
希望有帮助吧,嘻嘻(笔芯.jpg)

你可能感兴趣的:(最小生成树基础算法模板)