18 图的最小生成树—Kruskal算法

 图的最小生成树 Kruskal算法

最近小哼迷上了《龙门镖局》,从恰克图到武夷山,从张家口到老河口,从迪化到佛山,从蒙自到奉天,迤逦数千里的商道上,或车马,或舟楫,或驼驮,或肩挑,货物往来,钱财递送,皆离不开镖局押运。商号开在哪里,镖局便设在哪里。古代镖局的运镖,就是运货,也就是现代的物流。镖局每到一个新地方开展业务,都需要对运镖途中的绿林好汉进行打点。好说话的打点费就比较低,不好说话的打点费就比较高。现已知城镇地图如下,顶点是城镇编号,边上的值表示这条道路上打点绿林好汉需要的银子数。

换句话说,镖局的要求就是用最少的边让图连通(任意两点之间可以互相到达),其实就是将多余的边去掉。很显然,要想让有n个顶点的图连通,那么至少需要n-1 条边。如果一个连通无向图不包含回路,那么这就是一棵树,其实这里就是求一个图的最小生成树,下面就是两种生成树方案。

关键的问题是:如何选出这n-1条边,使得边的总长度之和最短呢?

既然要求是让边的总长度之和最短,我们自然可以想到首先选择最短的边,然后选择次短的边……直到选择了n-1条边为止。这就需要先对所有的边按照权值进行从小到大排序,然后从最小的开始选,依次选择每一条边,直到选择了n-1条边让整个图连通为止。将上图中的所有边排序之后如下。

选择过程如下:

到目前为止都很完美,直到选用236这条边时,我们发现此时2号顶点和3号顶点已经连通了,不再需要236这条边。如果加上这条边就会形成回路,那就不是树了,因此需要跳过这条边。

 接下来是457这条边,同理这条边我们也不能选用。

 

 最终选用349这条边后,我们已经选用了n-1条边,图已经连通,算法结束,如下。

算法总结:

(1) 将所有边按照权值进行排序

(2) 按照边的权值一一进行加入到图中,当发现两个顶点已经连通,选择的边丢弃,依次进行下去,直到加入了n-1条边

#include <bits/stdc++.h>
using namespace std;

struct edge { //定义边的结构体,方便后面的排序
	int u;
	int v;
	int w;
};
//此处认为边的数量至多9条
struct edge e[10];
//记录点和边的数量
int n, m;
//并查集用到的变量
int f[7], sum, _count = 0;
//f数组大小根据实际情况来确定,要比n的最大值大1
void quicksort(int left, int right) {
	int i, j;
	struct edge t;
	if (left > right) return;

	i = left ;
	j = right;
	while (i != j) {
		while (e[j].w >= e[left].w && i < j) j--;
		while (e[i].w <= e[left].w && i < j) i++;
		//交换
		if (i < j) {
			t = e[i];
			e[i] = e[j];
			e[j] = t;
		}
	}
	//最终将基准数归位,将left和i互换
	t = e[left];
	e[left] = e[i];
	e[i] = t;

	quicksort(left, i - 1);
	quicksort(i + 1, right);
	return;
}

//并查集寻找祖先的函数
int getf(int v) {
	if (f[v] == v)
		return v;
	else {
		//路径压缩
		f[v] = getf(f[v]);
		return f[v];
	}
}
//并查集合并两个子集合的函数
int merge(int v, int u) {
	int t1, t2;
	t1 = getf(v);
	t2 = getf(u);
	if (t1 != t2) {
		f[t2] = t1;
		return 1;
	}
	return 0;
}


int main() {
	cin >> n >> m;

	for (int i = 1; i <= m; i++)
		cin >> e[i].u >> e[i].v >> e[i].w;

	quicksort(1, m);

	cout << endl;

//	for (int i = 1; i <= m; i++) {
//		cout << e[i].u << " " << e[i].v << " " << e[i].w << endl;
//
//	}
	//并查集初始化
	for (int i = 1; i <= n; i++)
		f[i] = i;

	//Kruskal算法的核心部分
	for (int i = 1; i <= m; i++) {
		//判断一条边的两个顶点是否已经连通
		if (merge(e[i].u, e[i].v)) {
			_count++;
			sum = sum + e[i].w;
		}
		if (_count == n - 1) break;

	}
	cout << sum;

	return 0;
}

测试数据

6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2

输出结果  19

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值