最小生成树的Prim和Kruskal算法

一、Prim算法

1.算法原理:

和Dijkstra算法非常相似,都是从某个顶点开始,通过将边添加到新的集合中去。此算法添加边的原则描述如下:

设新的节点集合为V,由新的节点集合确定的边集为E = { (i, j) | i, j ∈V } (当然这些边构成的图是一棵树),每次将与V集合相连的点的不在E集合的最小边添加到E中去,同时将相连的点添加到V中去,在这个过程中,从原来的图中删除点和边。在添加的过程中还要注意不能有环的产生

2.难点:

这个算法主要的难点在于如何高效的确定我们需要添加的边,如果每次都遍历V集合的所有相关边,那么算法的复杂度比较高,O(n^2);但是我们可以借鉴在Dijkstra中的处理方法,通过队列来管理我们需要使用的边。优化以后算法复杂度变为O(elogv)

int cost[maxn][maxn];	//表示边权值
int mincost[maxn];	//从集合V出发的边每个顶点的最小权值
bool used[maxn];	//表示顶点i是否被取出,包含在V中
int vertex_num;	//顶点数

int Prim() {
	//未加入的点的边权值为INF
	fill(mincost, mincost + vertex_num, INF);
	fill(used, used + vertex_num, false);

	mincost[0] = 0;
	int res = 0;

	while (true) {
		int v = -1;

		for (int u = 0; u < vertex_num; u++) {
			if (!used[u] && (v == -1 || mincost[u] < mincost[v])) v = u;
		}

		if (v == -1) break;
		used[v] = true;
		res += mincost[v];

		for (int u = 0; u < vertex_num; u++) {
			if (!used[u]) mincost[u] = min(mincost[u], cost[v][u]);
		}
	}
	return res;
}

关于如下语句的解释为:对于没有加入V的点,如果该点与已经在V中的点有连接,那么使用边权值进行最小值的更新(已经在V中的点就没有必要更新了,因为更新也没有意义)

if (!used[u]) mincost[u] = min(mincost[u], cost[v][u]);

Prim算法的队列维护方式:

typedef pair<int, int> P;	//first: mincost, second: vertex
int cost[maxn][maxn];
bool used[maxn];
int vertex_num;

int Prime() {
	priority_queue<P, vector<P>, greater<P> > que;
	que.push(P(0, 0));
	int v = -1, ans;
	while (true) {
		if (!que.empty()) {
			v = que.top().second;
			ans += que.top().first;
			que.pop();
		}

		if (v == -1) break;
		used[v] = true;

		for (int u = 0; u < vertex_num; u++) {
			if (!used[u]) que.push(P(cost[v][u], u));
		}
	}
	return ans;
}


二、Kruskal算法

1.算法原理:

Kruskal算法按照边的权值顺序从小到大查看一遍,如果没有圈产生,就把当前边加入到生成树中

2.算法实现难点:

主要是判断是否产生圈,在prim算法中,我们是对节点数进行考虑,有一个关于节点状态的数组used[maxn]为我们排除那些已经被选取的点,然而这里只用到边,所以没有直接快速的方法去确定,但是我们可以利用并查集来排除产生环的情况,算法复杂度为O(elogv)

struct Edge {
	int u, v, cost;
};

int vertex_num, edge_num;	//顶点数,边数
int pre_V[maxn_V];	//记录顶点并查集
int rank[maxn_V];	//记录阶数
Edge edge[maxn_E];	//记录边

bool comp(const Edge& lhs, const Edge& rhs) {
	return lhs.cost < rhs.cost;
}

void init_union_find(int n) {
	for (int i = 0; i < vertex_num; i++) pre_V[i] = i;
}

int find(int value) {
	if (pre_V[value] == value) return value;
	return pre_V[value] = find(pre_V[value]);
}

void unite(int lhs, int rhs) {
	lhs = find(lhs);
	rhs = find(rhs);
	if (lhs == rhs) return ;
	if (rank[lhs] < rank[rhs]) {
		pre_V[lhs] = rhs;
	}
	else {
		pre_V[rhs] = lhs;
		if (rank[rhs] == rank[lhs]) rank[lhs]++;
	}
}

bool same(int lhs, int rhs) {
	return find(lhs) == find(rhs);
}

int Kruskal() {
	sort(edge, edge + edge_num, comp);
	init_union_find(vertex_num);
	int ans = 0;
	for (int i = 0; i < edge_num; i++) {
		Edge e = edge[i];
		if (!same(e.u, e.v)) {
			unite(e.u, e.v);
			ans += e.cost;
		}
	}
	return ans;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值