zoukankan      html  css  js  c++  java
  • 最小生成树算法

    最小生成树简介

    简单说就是在一个带权连通图(一般是无向图)里面生成一个树,使得所生成的树具有最小的权重之和,谓之最小生成树,这点很容易理解,因为我们在构建树的时候,不存在环,所以图的任意两个顶点之间最多存在一条边,所以到最后生成的树一定具备的特征是:删减了一部分原来图当中的edge,这个也就是我们要的结果:生成权重之和最小的树,也就是最小生成树,基于最小生成树的特征,可以预见的是,最小生成树不一定是一个二叉树

    最小生成树的两个算法

    1. Prime算法

    • 思想很简单,就是在一个给定的图当中我们随意指定一个起始点作为当前MST的根,然后依次在找到权值最小的边连接加入到树当中去,直至整个图的节点加入完毕。该算法从顶点的角度为出发点,时间复杂度为(O(n^2)),更适合与解决边的绸密度更高的连通网。

      算法分析过程在博客链接里面。

    • 代码

    #include<iostream>
    #include<fstream>
    using  namespace std;
     
    #define MAX 100
    #define MAXCOST 0x3f3f3f3f
     
    int graph[MAX][MAX];
     
    int prim(int graph[][MAX], int node)
    {
    	int lowcost[MAX];
    	int mst[MAX];
    	int i, j, min, minid, sum = 0;
    	for (i = 2; i <= node; i++)
    	{
    		lowcost[i] = graph[1][i];
    		mst[i] = 1;
    	}
    	mst[1] = 0;
    	for (i = 2; i <= node; i++)
    	{
    		min = MAXCOST;
    		minid = 0;
    		for (j = 2; j <= node; j++)
    		{
    			if (lowcost[j] < min && lowcost[j] != 0)
    			{
    				min = lowcost[j];
    				minid = j;
    			}
    		}
    		cout << "V" << mst[minid] << "-V" << minid << "=" << min << endl;
    		sum += min;
    		lowcost[minid] = 0;
    		for (j = 2; j <= node; j++)
    		{
    			if (graph[minid][j] < lowcost[j])
    			{
    				lowcost[j] = graph[minid][j];
    				mst[j] = minid;
    			}
    		}
    	}
    	return sum;
    }
     
    int main()
    {
    //注意我们的节点从1开始的
    	int i, j, k, m, n;
    	int cost;
    	ifstream in("input.txt");
    	in >> m >> n;//m=顶点的个数,n=边的个数
    	//初始化图G
    	for (i = 1; i <= m; i++)
    	{
    		for (j = 1; j <= m; j++)
    		{
    			graph[i][j] = MAXCOST;
    		}
    	}
    	//构建图G
    	for (k = 1; k <= n; k++)
    	{
    		in >> i >> j >> cost;
    		graph[i][j] = cost;
    		graph[j][i] = cost;
    	}
    	//求解最小生成树
    	cost = prim(graph, m);
    	//输出最小权值和
    	cout << "最小权值和=" << cost << endl;
    	system("pause");
    	return 0;
    }
    

    2. Kruskal算法

    • 基于并查集的一个算法,同时利用贪心策略,并查集在之前介绍并查集
      如果说prime算法是一个指定树的根然后递进延伸的话,那么Kruskal算法就是另外一个思想:分治、归并,也就是并查集的思想,基本的思路是:
      首先找到两个node,符合这两个node之间的路径长度最小,依次排序从小到大类推,知道所有的node全部都加入连接当中,连接结束的标志就是,在n个node之间连接了n-1条edge,那么我们构建的最小生成树也就随即完成了,因为本算法跟前面的prime不太一样,前面是一个连贯的递进过程,这个过程是一个分治连接的过程,我们需要在连接的各个部分之间判断是否存在环,如果是的话,应该舍弃连接,整个过程符合并查规律。

    • 基于上述描述,大致的代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int n,m,tot=0,k=0;//n端点总数,m边数,tot记录最终答案,k已经连接了多少边 
    int fat[200010];//记录集体老大 
    struct node
    {
    	int from,to,dis;//结构体储存边 
    }edge[200010];
    bool cmp(const node &a,const node &b)//sort排序(当然你也可以快排) 
    {
    	return a.dis<b.dis;
    }
    int father(int x)//找集体老大,并查集的一部分 
    {
    	if(fat[x]!=x)
    	return father(fat[x]);
    	else return x;
    }
    void unionn(int x,int y)//加入团体,并查集的一部分 
    {
    	fat[father(y)]=father(x);
    }
    int main()
    {
    	scanf("%d%d",&n,&m);//输入点数,边数 
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].dis);//输入边的信息 
    	}
    	for(int i=1;i<=n;i++) fat[i]=i;//自己最开始就是自己的老大 (初始化) 
    	sort(edge+1,edge+1+m,cmp);//按权值排序(kruskal的体现) 
    	for(int i=1;i<=m;i++)//从小到大遍历 
    	{
    		if(k==n-1) break;//n个点需要n-1条边连接 
    		if(father(edge[i].from)!=father(edge[i].to))//假如不在一个团体 
    		{
    			unionn(edge[i].from,edge[i].to);//加入 
    			tot+=edge[i].dis;//记录边权 
    			k++;//已连接边数+1 
    		}
    	}
    	printf("%d",tot);
    	return 0;
    }
    
    抬起头,永远年轻,永远热泪盈眶!
  • 相关阅读:
    jenkins+newman+postman实现api自动化
    数据库的关闭方法
    获取2台linux机器的时间差
    性能测试与其分析
    todo:云数据库的元信息
    todo:trove命令使用
    syslog协议及rsyslog服务全解析
    C++Primer学习日记
    excel-填充
    excel-删除
  • 原文地址:https://www.cnblogs.com/marvin-Hua-manlou/p/14085073.html
Copyright © 2011-2022 走看看