最小生成树 prim算法和kruscal算法

最小生成树 prim算法和kruscal算法

背景:用最小的路径把所有的节点连接起来,即在所有节点间构建一颗最小生成树。

怎么构造一条最小生成树呢,大概有两种思路。1.逐步添加点到树中 2.逐步添加边到树中

前者即是prim算法的思路,而后者则对应kruscal算法。

prim算法

prim算法的核心在于用一个数组表示所有节点到树的距离(minDist数组),然后依次把离节点最近的节点添加到树中,添加节点的同时,其他的节点离树的最短距离可能因为新节点的加入而改变。

因此prim算法的大致步骤分为:

​ 1.选择离树最近的节点

​ 2.将树添加到树中

​ 3.更新minDist数组

下面以例题对prim算法进行分析,具体细节见代码

习题链接

#include<iostream>
using namespace std;
#include<vector>
#include<climits>
int main() {
    int v, e;//分别表示定点数和边数
    cin >> v >> e;
    //,用二维数组来存储边的权值,题目说两点之间距离最大为10000
    vector<vector<int>>val(v + 1, vector<int>(v + 1, 10001));
    for (int i = 1; i <= e; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        val[a][b] = c;
        val[b][a] = c;
    }
    vector<int>minDist(v + 1, 10001);
    vector<bool>inTree(v + 1, false);
    //当进行v-1次遍历时,已经添加了v-1个节点,所有节点离树的距离已更新完毕
    for (int i = 1; i < v; i++) {
        //第一步,找到离树最近的节点,已经在树中的节点不用更新
        int pos = -1;
        int dist = INT_MAX;
        for (int j = 1; j <= v; j++) {
            if (!inTree[j] && minDist[j] < dist) {
                pos = j;
                dist = minDist[j];
            }
        }
        //第二步,将节点加入树中
        inTree[pos] = true;
        //第三步,更新节点离树的距离
        for (int j = 1; j <= v; j++) {
            if (!inTree[j] && val[pos][j] < minDist[j]) {
                minDist[j] = val[pos][j];
            }
        }
         
    }
    int res = 0;
    //注意我们第一个节点初始为10001,而事实上第一个节点到树的距离为0,从
    for (int i = 2; i <= v; i++) {
        res += minDist[i];
    }
    cout << res << endl;
    return 0;
}

思考:怎么把树的每条边输出呢?

回忆prim算法的三个步骤,我们是在第三个步骤中更新了节点到树的最短距离,是把pos节点到j节点的距离赋值给了minDist[j],因此我们只需再用一个parent数组,在更新j节点到树最短距离的同时,指明j节点连接的是哪个节点即可。

minDist[j] = val[pos][j];
parent[j]=pos;

kruscal算法

kruscal算法的思想是先对所有的边进行排序,然后逐次选择最短且符合条件的边不断放入集合。

如何选择最短的边,可以使用sort和compare()来对边的权值进行排序,也可以使用c++自带的数据结构multimap,我们知道map可以根据键值在插入时自动进行排序,为什么用multimap而不用map呢,因为我们这里是根据键值,即边的权值进行排序,比如有多条边权值为1,用map就只会保留最后一条权值为1的边。还有一个区别,因为可以有多个相同的键值,因此multimap不可以使用map[1]=val的形式输入数据,只能用insert。

这里的条件就是不能成环,例如已经添加了ac和bc,那么ab就不能再添加。看到这里,是不是思路的实现刚好符合并查集的功能。回顾一下并查集的功能:1.判断两个节点是否在同一个集合2.将两个节点添加到同一个集合。

下面以例题对kruscal算法进行分析,具体细节见代码

习题链接

#include<iostream>
using namespace std;
#include<vector>
#include<climits>
#include<map>
int v,e;//表示顶点和边
vector<int>parent;//并查集
//判断两节点是否在同一集合
bool issame(int i,int j){
	while(i!=parent[i])i=parent[i];
	while(j!=parent[j])j=parent[j];
	if(i==j)return true;
	return false;
}
//初始化
void init(){
	for(int i=0;i<=v;i++)parent[i]=i;
}
//将节点添加到并查集
void add(int i,int j){
	while(i!=parent[i])i=parent[i];
	while(j!=parent[j])j=parent[j];
	parent[i]=j;
}
int main(){
	cin>>v>>e;
	parent.resize(v+1);
	init();
	multimap<int,pair<int,int>>val;//存储边
	for(int i=0;i<e;i++){
		int a,b,c;
		cin>>a>>b>>c;
		val.insert(make_pair(c,make_pair(a,b)));
	}
	int res=0;
	for(auto it=val.begin();it!=val.end();it++){
		int a=it->second.first;
		int b=it->second.second;
		if(issame(a,b))continue;
		add(a,b);
		res+=it->first;
	}
	cout<<res<<endl;
	return 0;
}

总结

我们知道prim算法是点优先,而kruscal算法是边优先,主要的时间复杂度来自于对边进行快速排序,时间复杂度为O(n*log n),所以对于边相对来说比较少的情况,用kruscal算法。当边相对较多时,图更趋近于完全图,而我们实现prim算法时的时间复杂度和空间复杂度都只与点有关,时间复杂度为O(n²),用prim算法更优。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值