刚把最小生成树的两种算法学习了下,代码参考博主最小生成树算法,博主介绍的很详细,基本掌握了,自己将部分代码段添加了注释,留后续复习用
一、Prime算法
#include<iostream>
#include<string>
#include<vector>
using namespace std;
//首先是使用邻接矩阵完成Prim算法
struct Graph {
int vexnum; //顶点个数
int edge; //边的条数
int** arc; //邻接矩阵 由于是矩阵,使用int **,指向指针的指针
string* information; //记录每个顶点名称
};
//创建图
void createGraph(Graph& g) {
cout << "请输入顶点数:输入边的条数" << endl;
cin >> g.vexnum;
cin >> g.edge; //输入边的条数
g.information = new string[g.vexnum];
g.arc = new int* [g.vexnum];
int i = 0;
//开辟空间的同时,进行名称的初始化
for (i = 0; i < g.vexnum; i++) {
g.arc[i] = new int[g.vexnum];
g.information[i] = "v" + std::to_string(i + 1);//对每个顶点进行命名
for (int k = 0; k < g.vexnum; k++) {
g.arc[i][k] = INT_MAX; //初始化我们的邻接矩阵
}
}
cout << "请输入每条边之间的顶点编号(顶点编号从1开始),以及该边的权重:" << endl;
for (i = 0; i < g.edge; i++) {
int start;
int end;
cin >> start; //输入每条边的起点
cin >> end; //输入每条边的终点
int weight;
cin >> weight;
g.arc[start - 1][end - 1] = weight;//无向图边对称的
g.arc[end - 1][start - 1] = weight;
}
}
//打印图
void print(Graph g) {
int i;
for (i = 0; i < g.vexnum; i++) {
//cout << g.information[i] << " ";
for (int j = 0; j < g.vexnum; j++) {
if (g.arc[i][j] == INT_MAX)
cout << "∞" << " ";
else
cout << g.arc[i][j] << " ";
}
cout << endl;
}
}
//作为记录边的信息,这些边都是达到end的所有边中,权重最小的那个
struct Assis_array {
int start; //边的终点
int end; //边的起点
int weight; //边的权重
};
//进行prim算法实现,使用的邻接矩阵的方法实现。
void Prim(Graph g, int begin) {
//close_edge这个数组记录到达某个顶点的各个边中的权重最大的那个边
Assis_array* close_edge = new Assis_array[g.vexnum];
int j;
//进行close_edge的初始化,从开始起点进行初始化
for (j = 0; j < g.vexnum; j++) {
if (j != begin - 1) {
close_edge[j].start = begin - 1;
close_edge[j].end = j;
close_edge[j].weight = g.arc[begin - 1][j];
}
}//初始化操作完成了从起点到其余各点的起始点权重的赋值
close_edge[begin - 1].weight = -1;//从起点开始算,起点权重为-1,表示已经访问过,已经在集合U中
//访问剩下的顶点,并加入依次加入到集合U
for (j = 1; j < g.vexnum; j++) {
int min = INT_MAX;
int k;
int index;
//寻找数组close_edge中权重最小的那个边
for (k = 0; k < g.vexnum; k++) {
if (close_edge[k].weight != -1) {
if (close_edge[k].weight < min) {
min = close_edge[k].weight;
index = k;
}
}
}
//将权重最小的那条边的终点也加入到集合U
close_edge[index].weight = -1;
//输出对应的边的信息
cout << g.information[close_edge[index].start]
<< "-----"
<< g.information[close_edge[index].end]
<< "="
<< g.arc[close_edge[index].start][close_edge[index].end]
<< endl;
//更新我们的close_edge数组。
for (k = 0; k < g.vexnum; k++) {
if (g.arc[close_edge[index].end][k] < close_edge[k].weight)//将刚访问完的节点指向未访问的结点,判段边权是否比之前close_edge中的小
{
close_edge[k].weight = g.arc[close_edge[index].end][k];
close_edge[k].start = close_edge[index].end;
close_edge[k].end = k;
}
}
}
}
int main()
{
Graph g;
createGraph(g);//基本都是无向网图,所以我们只实现了无向网图
print(g);
Prim(g, 1);
system("pause");
return 0;
}
二、Kruskal算法
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
//检验输入边数和顶点数的值是否有效,可以自己推算为啥:
//顶点数和边数的关系是:((Vexnum*(Vexnum - 1)) / 2) < edge
bool check(int Vexnum, int edge) {
if (Vexnum <= 0 || edge <= 0 || ((Vexnum * (Vexnum - 1)) / 2) < edge)
return false;
return true;
}
//判断我们每次输入的的边的信息是否合法
//顶点从1开始编号
bool check_edge(int Vexnum, int start, int end, int weight) {
if (start<1 || end<1 || start>Vexnum || end>Vexnum || weight < 0) {
return false;
}
return true;
}
//边集结构,用于保存每条边的信息
typedef struct edge_tag {
bool visit; //判断这条边是否加入到了最小生成树中
int start; //该边的起点
int end; //该边的终点
int weight; //该边的权重
}Edge;//typedef是为了使用struct更加方便,如果没有typedef Edge是个变量,如果有typedef,Edge是结构体类型
//创建一个图,但是图是使用边集结构来保存
void createGraph(Edge*& e, int Vexnum, int edge) {
e = new Edge[edge];//为每条边集开辟空间
int start = 0;
int end = 0;
int weight = 0;
int i = 0;
cout << "输入每条边的起点、终点和权重:" << endl;
while (i != edge)
{
cin >> start >> end >> weight;
while (!check_edge(Vexnum, start, end, weight)) {
cout << "输入的值不合法,请重新输入每条边的起点、终点和权重:" << endl;
cin >> start >> end >> weight;
}
e[i].start = start;
e[i].end = end;
e[i].weight = weight;
e[i].visit = false; //每条边都还没被初始化
++i;
}
}
//我们需要对边集进行排序,排序是按照每条边的权重,从小到大排序。
int cmp(const void* first, const void* second) {
return ((Edge*)first)->weight - ((Edge*)second)->weight;//由于qsort要接受的是void*,这里使用了强制类型转换,将void*转为Edge*
}
//好了,我们现在需要做的是通过一定的方式来判断
//如果我们把当前的边加入到生成树中是否会有环出现。
//通过我们之前学习树的知识,我们可以知道如果很多棵树就组成一个森林,而且
//如果同一颗树的两个结点在连上一条边,那么就会出现环,
//所以我们就通过这个方式来判断加入了一个新的边后,是否会产生环,
//开始我们让我们的图的每个顶点都是一颗独立的树,通过不断的组合,把这个森林变
//成来源于同一颗顶点的树
//如果不理解,画个图就明白了, 判断生成树中是否有环存在,只要知道同一棵树上的两个顶点只要连上了边就存在环,将独立的树变为森林
//首先是找根节点的函数,
//其中parent代表顶点所在子树的根结点
//child代表每个顶点孩子结点的个数
int find_root(int child, int* parent) {
//此时已经找到了该顶点所在树的根节点了
if (parent[child] == child) { //自己就是根节点
return child;
}
//往前递归,寻找它父亲的所在子树的根结点
parent[child] = find_root(parent[child], parent);
return parent[child];
}
//合并两个子树
bool union_tree(Edge e, int* parent, int* child) {
//先找出改边所在子树的根节点
int root1;
int root2;
//记住我们顶点从1开始的,所以要减1
root1 = find_root(e.start - 1, parent);
root2 = find_root(e.end - 1, parent);
//只有两个顶点不在同一颗子树上,才可以把两棵树并未一颗树
if (root1 != root2) {
//小树合并到大树中,看他们的孩子个数
if (child[root1] > child[root2]) {
parent[root2] = root1;
//大树的孩子数量是小树的孩子数量加上
//大树的孩子数量在加上小树根节点自己
child[root1] += child[root2] + 1;
}
else {
parent[root1] = root2;
child[root2] += child[root1] + 1;
}
return true;
}
return false;
}
//克鲁斯卡算法的实现
void Kruskal() {
int Vexnum = 0;
int edge = 0;
cout << "请输入图的顶点数和边数:" << endl;
cin >> Vexnum >> edge;
while (!check(Vexnum, edge)) {
cout << "你输入的图的顶点数和边数不合法,请重新输入:" << endl;
cin >> Vexnum >> edge;
}
//声明一个边集数组
Edge* edge_tag;
//输入每条边的信息
createGraph(edge_tag, Vexnum, edge);
int* parent = new int[Vexnum]; //记录每个顶点所在子树的根节点下标
int* child = new int[Vexnum]; //记录每个顶点为根节点时,其有的孩子节点的个数
int i;
for (i = 0; i < Vexnum; i++) {
parent[i] = i;//下标最初初始化为自身下标 初始化操作使得每个点是孤立的
child[i] = 0;
}
//对边集数组进行排序,按照权重从小到达排序
qsort(edge_tag, edge, sizeof(Edge), cmp);
int count_vex; //记录输出的边的条数
count_vex = i = 0;
while (i != edge) {
//如果两颗树可以组合在一起,说明该边是生成树的一条边
if (union_tree(edge_tag[i], parent, child)) { //从这里将散点变为树
cout << ("v" + std::to_string(edge_tag[i].start))
<< "-----"
<< ("v" + std::to_string(edge_tag[i].end))
<< "="
<< edge_tag[i].weight
<< endl;
edge_tag[i].visit = true;
++count_vex; //生成树的边加1
}
//这里表示所有的边都已经加入成功
if (count_vex == Vexnum - 1) {
break;
}
++i;
}
if (count_vex != Vexnum - 1) {
cout << "此图为非连通图!无法构成最小生成树。" << endl;
}
delete[] edge_tag;
delete[] parent;
delete[] child;
}
int main() {
Kruskal();
system("pause");
return 0;
}
原理的话我觉得另外一篇博文介绍的也不错,简单易懂,可供参考最小生成树算法图解