温故而知新 Blog by ssj233
浅谈图の最小生成树
2022-07-17 / 3 min read

最小生成树

最小生成树算法有2种

prim (点数^2)

kruskal (边^log边)

prim

其法:

  1. 把1放入集合中
  2. 找集合中的点的邻接点中边最小的(没有标记过的)
  3. 把此邻接点放入集合
  4. 标记
  5. 重复2,直到所有节点都被放进集合中
vector<int>d;
while(所有节点并没有都放d中)
{
	for(i遍历d中所有已入的节点)
	{
		for(遍历i的邻接点)
		{
			if(i到j的权值是最小的&&边没有标记&&j没有标记)
			{
				保存i,j;
			}
		}
	}
	标记j;
	标记边;
	总和+=i到j的权值
}

具体地

while ((int)d.size() < n)//集合d
{
	edge mx{-1, 114514};//.first 记录j   .second 记录最大权值
	int aa;//记录i
	for (auto i : d)//遍历集合
		for (auto j : a[i])//遍历i的邻接点
			if (mx.second > j.second && !e[i][j.first] && !e[j.first][i]&& !b[j.first])
				mx = j, aa = i;//找最小的,没有标记的
	d.push_back(mx.first);//放入集合
	b[aa] = b[mx.first] = 1;//标记
	e[aa][mx.first] = 1;//标记
	s += mx.second;//总和+
}

kruskal

其法:
建立并查集fa数组记录个点的父亲,初始化为自己(fa[2]=2)
一个找父亲的函数ff()

for(遍历所有边)
{
1. 看边数有没有等于n-1(树的性质),则break
2. 找没被选过的最小边
1. 看他们是否有被链接过(ff(from)==ff(to)),没有则让to的父亲重置为from
2. 边数加一
3. 总和加上全值
}
因为找最小的,可以考虑用堆或sort优化,
推荐用sort,,trust me!

cpp code如下

int n;//总点数
int m;//总边数
struct ee
{
	int f, t;//from and to
	double v;//权值
	ee() {}
	ee(int a, int b, double c) : f(a), t(b), v(c) {}//构造函数
	friend bool operator<(ee a, ee b) { return a.v < b.v; }//重载以排序
} b[N];

int ff(int x)//用递归找父亲
{
	if (fa[x] != x)
		fa[x] = ff(fa[x]);
	return fa[x];
}

sort(b + 1, b + 1 + m);//按权排序边

for (int i = 1; i <= m; i++)//遍历边
{
	if (bs == (n - 1))//边数==点数-1
	{
		printf("Need %.2lf miles of cable\n", sum);
		break;
	}
	f = ff(b[i].f), ft = ff(b[i].t);
	if (f != ft)//判断父亲是否相同
		fa[ft] = f, sum += b[i].v, bs++;//重置父亲,总和+,边数+1
	}