最小生成树之PRIM算法&Kruskal算法C++代码(详细注释)及复杂度分析

12 篇文章 0 订阅

最小生成树之PRIM算法C++代码实现:

#include <iostream>
#include <vector>
using namespace std;

void prim(vector<vector<int>>& VGraph, vector<int>& lowcost, vector<int>& closest, vector<bool>& visited)  
{
	int size = lowcost.size(); //M
	visited[0] = true;  //从顶点0出发构造最小生成树
	for (int i = 1; i < size; i++)
	{
		lowcost[i] = VGraph[0][i];  //初始化为顶点0到其他顶点i的权
		closest[i] = 0;  //i节点依附的节点
		visited[i] = false;
	}
	int weight = 0; //最小生成树权值

	cout << "输出生成树上各条边:" << endl;
	//m-1条边,循环m-1次,每次找到一条边
	for (int i = 0; i < size-1; i++)
	{
		int min = 99999;
		int index = 1;
		//寻找具有最小代价的边
		for (int j = 0; j < size; j++)
		{
			//从刚并入U的顶点到其他点j (j必须未被访问过)的权中选出具有最小代价的边
			if (lowcost[j] < min && !visited[j])
			{
				min = lowcost[j];
				index = j;
			}
		}
		weight += min;
		//打印每次选择的边
		cout << "(" << closest[index] << "," << index << ")" << endl;

		visited[index] = true;
		//因为新加入了index点,所以要查找新加入的index点到未在S中的点K中的权值是不是可以因此更小
		for (int j = 1; j < size; j++)  
		{
			if ((VGraph[index][j] < lowcost[j]) && (!visited[j]))  //lowcost表示从已知树到某一点所需付出的最小权值
			{
				lowcost[j] = VGraph[index][j]; //更新刚加入的index点到其他j点的权值
				closest[j] = index;  //将j节点依附的节点修改为index
			}
		}
	}
	cout << "\n最小生成树权值为:" << weight << endl;
}
int main()
{
	int M, N;   //M为顶点数  N为边数
	cin >> M >> N;  
	vector<vector<int>> VGraph(M,vector<int>(M)); //图的邻接矩阵
	vector<int> lowcost(M);  //刚选中并入U的顶点到其他顶点的权,初始为顶点0到其他点的权,因为从0出发
	vector<int> closest(M);  //记录每个节点依附的具有最小代价的节点,由此可得到最小生成树上的所有边
	vector<bool> visited(M);  //标记节点是否已访问
	//数组表示  邻接矩阵初始化
	for (int i = 0; i < M; i++)
	{
		for (int j = 0; j < M; j++)
		{
			VGraph[i][j] = 99999;
		}
	}
	for (int i = 0; i < N; i++)
	{
		int a, b;  //两个顶点
		cin >> a >> b;
		int length;  //权
		cin >> length;
		VGraph[a][b] = VGraph[b][a] = length; //初始化图各条边
	}
	prim(VGraph, lowcost, closest, visited);
}
//0 1 6 0 2 1 0 3 5 1 2 5 1 4 3 2 3 5 2 4 6 2 5 4 3 5 2 4 5 6 

在这里插入图片描述
复杂度分析:

假设网中有m个顶点,则第一个初始话的循环语句频度为m; 第二个找m-1条边的循环语句频度为m-1, 其中有两个内循环:其一是求具有最小代价的边,其频度为m, 其二是重新选择具有最小代价的边,其频度为m。 因此,PRIM算法总的时间复杂度为O(m^2), 与网中的边数无关,因此适用于求边稠密的网的最小生成树。

Kruskal算法

并查集实现


//并查集
vector<int> father(100);
vector<int> Rank(100);
//初始化
void init(int n){  //n个节点
    for(int i=0;i<n;++i){
        father[i] = i;
        Rank[i] = 1;
    }
}
//查询节点的根节点(路径压缩)
// int find(int x){
//     if(x==father[x]){
//         return x;
//     }
//     father[x] = find(father[x]); //查询过程中把沿途的每个节点的父节点都设为根节点
//     return father[x];
// }

//非递归
int find(int x){
    int root = father[x];
    while(root != father[root]){  //找根节点
        root = father[root];
    }
    //将路径上的所有节点的父节点设为根节点
    while(x != root){
        int t = father[x];
        father[x] = root;
        x = t;
    }
    return root;
}

//合并
void unit(int x,int y){
    int fx = find(x);
    int fy = find(y);   //先找到两个根节点
    if(Rank[fx]<=Rank[fy]){
        father[fx] = fy;
    }
    else{
        father[fy] = fx;
    }
    if(Rank[fx]==Rank[fy] && fx != fy){
        Rank[fy]++;
    }
}

struct edge{
    int start;
    int end;
    int weight;
    edge(){}
    edge(int s,int e,int w):start(s),end(e),weight(w){}
};

bool cmp(edge& a,edge& b){
    return a.weight<b.weight;
}

int main(){
	int m,n;  //顶点,边
    cin>>m>>n;
    // vector<vector<int>> G(m,vector<int> (m,INT_MAX));
    vector<edge> G;
    //图初始化
    int a,b,w;
    for(int i=0;i<n;++i){
        cin>>a>>b>>w;
        // G[a][b] = G[b][a] = length;
        edge e(a,b,w);
        G.push_back(e);
    }
    //kruskal
    vector<int> res;  //存最小生成树的每条边的索引
    int res_weight = 0;
    sort(G.begin(),G.end(),cmp);  //边排序
    init(m);  //并查集初始化    
    int nEdge = 0;  //最小生成树中的边数:m-1条
    for(int i = 0; i < n && nEdge != m-1; ++i){
        if(find(G[i].start) != find(G[i].end)){
            unit(G[i].start,G[i].end);
            res.push_back(i);
            res_weight += G[i].weight;
            nEdge++;
        }
    }
    //如果加入边的数量小于m - 1,则表明该无向图不连通,等价于不存在最小生成树
    if(nEdge < m-1){
        cout<<"error!"<<endl;
        return 0;
    }
    for(int i=0;i<m-1;++i){
        cout<<G[res[i]].start<<" "<<G[res[i]].end<<ends;
    }
    cout<<endl;
    cout<<res_weight<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值