C++使用Kruskal和Prim算法实现最小生成树

所属分类: 软件编程 / C 语言 阅读数: 109
收藏 0 赞 0 分享

很久以前就学过最小生成树之Kruskal和Prim算法,这两个算法很容易理解,但实现起来并不那么容易。最近学习了并查集算法,得知并查集可以用于实现上述两个算法后,我自己动手实现了最小生成树算法。

宏观上讲,Kruskal算法就是一个合并的过程,而Prim算法是一个吞并的过程,另外在Prim算法中还用到了一种数据结构——优先级队列,用于动态排序。由于这两个算法很容易理解,在此不再赘述。接下来给出我的源代码。

输入

第一行包含两个整数n和m,n表示图中结点个数,m表示图中边的条数;接下来m行,每一行包含三个整数u,v,w,表示途中存在一条边(u,v),并且其权重为w;为了便于调试,我的程序是从文件中输入数据的;

输出

输出程序选择的最小生成树的权值之和以及每一条边信息;

Kruskal算法:

#include <iostream>
#include <vector>
#include <algorithm>
#include <fstream>
using namespace std;
 
struct Edge
{
 int u; //边连接的一个顶点编号
 int v; //边连接另一个顶点编号
 int w; //边的权值
 friend bool operator<(const Edge& E1, const Edge& E2)
 {
 return E1.w < E2.w;
 }
};
//创建并查集
void MakeSet(vector<int>& uset, int n)
{
 uset.assign(n, 0);
 for (int i = 0; i < n; i++)
 uset[i] = i;
}
//查找当前元素所在集合的代表元
int FindSet(vector<int>& uset, int u)
{
 int i = u;
 while (uset[i] != i) i = uset[i];
 return i;
}
void Kruskal(const vector<Edge>& edges, int n)
{
 vector<int> uset;
 vector<Edge> SpanTree;
 int Cost = 0, e1, e2;
 MakeSet(uset, n);
 for (int i = 0; i < edges.size(); i++) //按权值从小到大的顺序取边
 {
 e1 = FindSet(uset, edges[i].u);
 e2 = FindSet(uset, edges[i].v);
 if (e1 != e2) //若当前边连接的两个结点在不同集合中,选取该边并合并这两个集合
 {
 SpanTree.push_back(edges[i]);
 Cost += edges[i].w;
 uset[e1] = e2; //合并当前边连接的两个顶点所在集合
 }
 }
 cout << "Result:\n";
 cout << "Cost: " << Cost << endl;
 cout << "Edges:\n";
 for (int j = 0; j < SpanTree.size(); j++)
 cout << SpanTree[j].u << " " << SpanTree[j].v << " " << SpanTree[j].w << endl;
 cout << endl;
}
int main()
{
 ifstream in("data.txt");
 
 int n, m;
 in >> n >> m;
 vector<Edge> edges;
 edges.assign(m, Edge());
 for (int i = 0; i < m; i++)
 in >> edges[i].u >> edges[i].v >> edges[i].w;
 sort(edges.begin(), edges.end()); //排序之后,可以以边权值从小到大的顺序选取边
 Kruskal(edges, n);
 
 in.close();
 
 system("pause");
 return 0;
}

Prim算法:

#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <queue>
using namespace std;
struct Node
{
 int v;
 int w;
 Node(int a, int b) :v(a), w(b){}
};
struct Edge
{
 int u;
 int v;
 int w;
 Edge(int a, int b, int c) :u(a), v(b), w(c){}
 friend bool operator<(const Edge& E1, const Edge& E2)
 {
 return E1.w>E2.w;
 }
};
int n, m;
vector<list<Node>> Adj;
priority_queue<Edge> Q;
 
int FindSet(vector<int>& uset, int v)
{
 int i = v;
 while (i != uset[i]) i = uset[i];
 return i;
}
 
void Prim()
{
 int Cost = 0; //用于统计最小生成树的权值之和
 vector<Edge> SpanTree; //用于保存最小生成树的边
 vector<int> uset(n,0); //用数组来实现并查集
 Edge E(0, 0, 0);
 for (int i = 0; i < n; i++) uset[i] = i; //并查集初始化
 for (auto it = Adj[0].begin(); it != Adj[0].end(); it++) 
 Q.push(Edge(0, it->v, it->w)); //根据Prim算法,开始时选取结点0,并将结点0与剩余部分的边保存在优先队列中
 //循环中每次选取优先队列中权值最小的边,并更新优先队列
 while (!Q.empty())
 {
 E = Q.top();
 Q.pop();
 if (0 != FindSet(uset, E.v))
 {
 Cost += E.w;
 SpanTree.push_back(E);
 uset[E.v] = E.u;
 for (auto it = Adj[E.v].begin(); it != Adj[E.v].end(); it++)
 if (0 != FindSet(uset, it->v)) Q.push(Edge(E.v, it->v, it->w));
 }
 }
 cout << "Result:\n";
 cout << "Cost: " << Cost << endl;
 cout << "Edges:\n";
 for (int j = 0; j < SpanTree.size(); j++)
 cout << SpanTree[j].u << " " << SpanTree[j].v << " " << SpanTree[j].w << endl;
 cout << endl;
}
int main()
{
 ifstream in("data.txt");
 
 int u, v, w;
 in >> n >> m;
 Adj.assign(n, list<Node>());
 for (int i = 0; i < m; i++)
 {
 in >> u >> v >> w;
 Adj[u].push_back(Node(v,w));
 Adj[v].push_back(Node(u,w));
 }
 Prim();
 
 in.close();
 
 system("pause");
 return 0;
}

就实现而言,Kruskal算法比Prim算法更容易,代码更易于理解。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

更多精彩内容其他人还在看

用标准c++实现string与各种类型之间的转换

这个类在头文件中定义, < sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本
收藏 0 赞 0 分享

C++如何通过ostringstream实现任意类型转string

再使用整型转string的时候感觉有点棘手,因为itoa不是标准C里面的,而且即便是有itoa,其他类型转string不是很方便。后来去网上找了一下,发现有一个好方法
收藏 0 赞 0 分享

C/C++指针小结

要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区
收藏 0 赞 0 分享

C++ 类的静态成员深入解析

在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象
收藏 0 赞 0 分享

C++类的静态成员初始化详细讲解

通常静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或是枚举型const,则可以在类声明中初始化
收藏 0 赞 0 分享

C++类静态成员与类静态成员函数详解

静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值
收藏 0 赞 0 分享

C++中的friend友元函数详细解析

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样
收藏 0 赞 0 分享

static全局变量与普通的全局变量的区别详细解析

以下是对static全局变量与普通的全局变量的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
收藏 0 赞 0 分享

C++ explicit关键字的应用方法详细讲解

C++ explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?下面就让我们一起来看看这方面的知识吧
收藏 0 赞 0 分享

教你5分钟轻松搞定内存字节对齐

随便google一下,人家就可以跟你解释的,一大堆的道理,我们没怎么多时间,讨论为何要对齐.直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则
收藏 0 赞 0 分享
查看更多