• 【algo&ds】8.最小生成树


    1.最小生成树介绍

    什么是最小生成树?

    最小生成树(Minimum spanning tree,MST)是在一个给定的无向图G(V,E)中求一棵树T,使得这棵树拥有图G中的所有顶点,且所有边都是来自图G中的边,并且满足整棵树的边权值和最小。

    2.prim算法

    和Dijkstra算法很像!!请看如下Gif图,prim算法的核心思想是对图G(V,E)设置集合S,存放已被访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中间点,优化所有从u能到达的顶点v与集合s之间的最短距离。这样的操作执行n次,直到集合s中包含所有顶点。

    prim

    不同的是,Dijkstra算法中的dist是从源点s到顶点w的最短路径;而prim算法中的dist是从集合S到顶点w的最短路径,以下是他们的伪码描述对比,关于Dijkstra算法的详细描述请参考文章

    Snipaste_2019-11-24_15-30-32

    算法实现:

    #include<iostream>
    #include<vector>
    #define INF 100000
    #define MaxVertex 105
    typedef int Vertex; 
    int G[MaxVertex][MaxVertex];
    int parent[MaxVertex];   // 并查集 
    int dist[MaxVertex]; // 距离 
    int Nv;    // 结点 
    int Ne;    // 边 
    int sum;  // 权重和 
    using namespace std; 
    vector<Vertex> MST;  // 最小生成树 
    
    // 初始化图信息 
    void build(){
    	Vertex v1,v2;
    	int w;
    	cin>>Nv>>Ne;
    	for(int i=1;i<=Nv;i++){
    		for(int j=1;j<=Nv;j++)
    			G[i][j] = 0;  // 初始化图 
    		dist[i] = INF;   // 初始化距离
    		parent[i] = -1;  // 初始化并查集 
    	}
    	// 初始化点
    	for(int i=0;i<Ne;i++){
    		cin>>v1>>v2>>w;
    		G[v1][v2] = w;
    		G[v2][v1] = w;
    	}
    }
    
    // Prim算法前的初始化 
    void IniPrim(Vertex s){
    	dist[s] = 0;
    	MST.push_back(s);
    	for(Vertex i =1;i<=Nv;i++)
    		if(G[s][i]){
    			dist[i] = G[s][i];
    			parent[i] = s;
    		} 
    }
    
    // 查找未收录中dist最小的点 
    Vertex FindMin(){
    	int min = INF;
    	Vertex xb = -1;
    	for(Vertex i=1;i<=Nv;i++)
    		if(dist[i] && dist[i] < min){ 
    			min = dist[i];
    			xb = i;
    		}
    	return xb;
    }
    
    void output(){
    	cout<<"被收录顺序:"<<endl; 
    	for(Vertex i=1;i<=Nv;i++)
    		cout<<MST[i]<<" ";
    	cout<<"权重和为:"<<sum<<endl; 
    	cout<<"该生成树为:"<<endl; 
    	for(Vertex i=1;i<=Nv;i++)
    		cout<<parent[i]<<" ";
    }
    
    void Prim(Vertex s){
    	IniPrim(s);
    	while(1){
    		Vertex v = FindMin();
    		if(v == -1)
    			break;
    		sum += dist[v];
    		dist[v] = 0;
    		MST.push_back(v);
    		for(Vertex w=1;w<=Nv;w++)
    			if(G[v][w] && dist[w])
    				if(G[v][w] < dist[w]){
    					dist[w] = G[v][w];
    					parent[w] = v;
    				}
    	}
    }
    
    
    int main(){
    	build();
    	Prim(1);
    	output();
    	return 0;
    } 
    

    关于prim算法的更加详细讲解请参考视频

    3.kruskal算法

    Kruskal算法也可以用来解决最小生成树的问题,其算法思想很容易理解,典型的边贪心,其算法思想为:

    • 在初始状态时隐去图中所有的边,这样图中每个顶点都是一个单独的连通块,一共有n个连通块
    • 对所有边按边权从小到大进行排序
    • 按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在同一个连通块中,则把这条测试边加入当前最小生成树中,否则,将边舍弃。
    • 重复执行上一步骤,直到最小生成树中的边数等于总顶点数减一 或者测试完所有边时结束;如果结束时,最小生成树的边数小于总顶点数减一,说明该图不连通。

    请看下面的Gif图!

    kruskal

    算法实现:

    #include<iostream>
    #include<string>
    #include<vector>
    #include<queue>
    #define INF 100000
    #define MaxVertex 105
    typedef int Vertex; 
    int G[MaxVertex][MaxVertex];
    int parent[MaxVertex];   // 并查集最小生成树 
    int Nv;    // 结点 
    int Ne;    // 边 
    int sum;  // 权重和 
    using namespace std; 
    struct Node{
    	Vertex v1;
    	Vertex v2;
    	int weight; // 权重 
    	// 重载运算符成最大堆 
    	bool operator < (const Node &a) const
    	{
    		return weight>a.weight;
    	}
    };
    vector<Node> MST;  // 最小生成树 
    priority_queue<Node> q;   // 最小堆 
    
    // 初始化图信息 
    void build(){
    	Vertex v1,v2;
    	int w;
    	cin>>Nv>>Ne;
    	for(int i=1;i<=Nv;i++){
    		for(int j=1;j<=Nv;j++)
    			G[i][j] = 0;  // 初始化图
    		parent[i] = -1;
    	}
    	// 初始化点
    	for(int i=0;i<Ne;i++){
    		cin>>v1>>v2>>w;
    		struct Node tmpE;
    		tmpE.v1 = v1;
    		tmpE.v2 = v2;
    		tmpE.weight = w;
    		q.push(tmpE); 
    	}
    }
    
    //  路径压缩查找 
    int Find(int x){
    	if(parent[x] < 0)
    		return x;
    	else
    		return parent[x] = Find(parent[x]);
    } 
    
    //  按秩归并 
    void Union(int x1,int x2){
    	if(parent[x1] < parent[x2]){
    		parent[x1] += parent[x2];
    		parent[x2] = x1;
    	}else{
    		parent[x2] += parent[x1];
    		parent[x1] = x2;
    	}
    } 
    
    void Kruskal(){
    	// 最小生成树的边不到 Nv-1 条且还有边 
    	while(MST.size()!= Nv-1 && !q.empty()){
    		Node E = q.top();  // 从最小堆取出一条权重最小的边
    		q.pop(); // 出队这条边 
    		if(Find(E.v1) != Find(E.v2)){  // 检测两条边是否在同一集合 
    			sum += E.weight; 
    			Union(E.v1,E.v2);     // 并起来 
    			MST.push_back(E);
    		}
    	}
    	
    } 
    
    
    void output(){
    	cout<<"被收录顺序:"<<endl; 
    	for(Vertex i=0;i<Nv;i++)
    		cout<<MST[i].weight<<" ";
    	cout<<"权重和为:"<<sum<<endl; 
    	for(Vertex i=1;i<=Nv;i++)
    		cout<<parent[i]<<" ";
    	cout<<endl;
    }
    
    
    int main(){
    	build();
    	Kruskal();
    	output();
    	return 0;
    } 
    

    关于kruskal算法更详细的讲解请参考视频

  • 相关阅读:
    atitit.nfc 身份证 银行卡 芯片卡 解决方案 attilax总结
    atitit.php 流行框架 前三甲为:Laravel、Phalcon、Symfony2 attilax 总结
    Atitit.执行cmd 命令行 php
    Atitit. 图像处理jpg图片的压缩 清理垃圾图片 java版本
    atitit。企业组织与软件工程的策略 战略 趋势 原则 attilax 大总结
    atitit. 管理哲学 大毁灭 如何防止企业的自我毁灭
    Atitit.java的浏览器插件技术 Applet japplet attilax总结
    Atitit.jquery 版本新特性attilax总结
    Atitit. 软件开发中的管理哲学一个伟大的事业必然是过程导向为主 过程导向 vs 结果导向
    (转)获取手机的IMEI号
  • 原文地址:https://www.cnblogs.com/ericling/p/11922816.html
Copyright © 2020-2023  润新知