前言
构造最小生成树有很多算法,但是都是利用了最小生成树的同一种性质:MST性质(假设N=(V,{E})是一个连通网,U是顶点集V的一个非空子集,如果(u,v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必定存在一颗包含边(u,v)的最小生成树),下面就介绍两种使用MST性质生成最小生成树的算法:普里姆算法和克鲁斯卡尔算法。
普里姆算法—Prim算法
算法思路:
首先就是从图中的一个起点a开始,把a加入U集合,然后,寻找从与a有关联的边中,权重最小的那条边并且该边的终点b在顶点集合:(V-U)中,我们也把b加入到集合U中,并且输出边(a,b)的信息,这样我们的集合U就有:{a,b},然后,我们寻找与a关联和b关联的边中,权重最小的那条边并且该边的终点在集合:(V-U)中,我们把c加入到集合U中,并且输出对应的那条边的信息,这样我们的集合U就有:{a,b,c}这三个元素了,一次类推,直到所有顶点都加入到了集合U。
普里姆算法—代码实现
(1)采用的是邻接矩阵的方式存储图,代码如下:
#include<iostream>
#include<string>
#include<vector>
using namespace std;
//首先是使用邻接矩阵完成Prim算法
struct Graph {
int vexnum; //顶点个数
int edge; //边的条数
int ** arc; //邻接矩阵
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中的值设置为-1,代表已经加入到集合U了
close_edge[begin - 1].weight = -1;
//访问剩下的顶点,并加入依次加入到集合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[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;
}
(2)采用的是邻接表的方式存储图,代码如下:
#include<iostream>
#include<string>
using namespace std;
//表结点
struct ArcNode {
int adjvex; //某条边指向的那个顶点的位置(一般是数组的下标)。
ArcNode * next; //指向下一个表结点
int weight; //边的权重
};
//头结点
struct Vnode {
ArcNode * firstarc; //第一个和该顶点依附的边 的信息
string data; //记录该顶点的信息。
};
struct Graph_List {
int vexnum; //顶点个数
int edge; //边的条数
Vnode * node; //顶点表
};
//创建图,是一个重载函数
void createGraph(Graph_List &g) {
cout << "请输入顶点数:输入顶点边的个数:" << endl;
cin >> g.vexnum;
cin >> g.edge;
g.node = new Vnode[g.vexnum];
int i;
for (i = 0; i < g.vexnum; i++) {
g.node[i].data = "v" + std::to_string(i + 1); //对每个顶点进行命名
g.node[i].firstarc = NULL;//初始化每个顶点的依附表结点
}
cout << "请输入每条边之间的顶点编号(顶点编号从1开始),以及该边的权重:" << endl;
for (i = 0; i < g.edge; i++) {
int start;
int end;
cin >> start; //输入每条边的起点
cin >> end; //输入每条边的终点
int weight;
cin >> weight;
ArcNode * next = new ArcNode;
next->adjvex = end - 1;
next->next = NULL;
next->weight = weight;
//如果第一个依附的边为空
if (g.node[start - 1].firstarc == NULL) {
g.node[start - 1].firstarc = next;
}
else {
ArcNode * temp; //临时表结点
temp = g.node[start - 1].firstarc;
while (temp->next) {//找到表结点中start-1这个结点的链表的最后一个顶点
temp = temp->next;
}
temp->next = next; //在该链表的尾部插入一个结点
}
//因为无向图边是双向的
ArcNode * next_2 = new ArcNode;
next_2->adjvex = start - 1;
next_2->weight = weight;
next_2->next = NULL;
//如果第一个依附的边为空
if (g.node[end - 1].firstarc == NULL) {
g.node[end - 1].firstarc = next_2;
}
else {
ArcNode * temp; //临时表结点
temp = g.node[end - 1].firstarc;
while (temp->next) {//找到表结点中start-1这个结点的链表的最后一个顶点
temp = temp->next;
}
temp->next = next_2; //在该链表的尾部插入一个结点
}
}
}
void print(Graph_List g) {
cout<<"图的邻接表:"<<endl;
for (int i = 0; i < g.vexnum; i++) {
cout << g.node[i].data << " ";
ArcNode * next;
next = g.node[i].firstarc;
while (next) {
cout << "("<< g.node[i].data <<","<<g.node[next->adjvex].data<<")="<<next->weight << " ";
next = next->next;
}
cout << "^" << endl;
}
}
作为记录边的信息,这些边都是达到end的所有边中,权重最小的那个
struct Assis_array {
int start; //边的终点
int end; //边的起点
int weight; //边的权重
};
void Prim(Graph_List g, int begin) {
cout << "图的最小生成树:" << endl;
//close_edge这个数组记录到达某个顶点的各个边中的权重最大的那个边
Assis_array *close_edge=new Assis_array[g.vexnum];
int j;
for (j = 0; j < g.vexnum; j++) {
close_edge[j].weight = INT_MAX;
}
ArcNode * arc = g.node[begin - 1].firstarc;
while (arc) {
close_edge[arc->adjvex].end = arc->adjvex;
close_edge[arc->adjvex].start = begin - 1;
close_edge[arc->adjvex].weight = arc->weight;
arc = arc->next;
}
//把起点的close_edge中的值设置为-1,代表已经加入到集合U了
close_edge[begin - 1].weight = -1;
//访问剩下的顶点,并加入依次加入到集合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;
}
}
}
//输出对应的边的信息
cout << g.node[close_edge[index].start].data
<< "-----"
<< g.node[close_edge[index].end].data
<< "="
<< close_edge[index].weight
<<endl;
//将权重最小的那条边的终点也加入到集合U
close_edge[index].weight = -1;
//更新我们的close_edge数组。
ArcNode * temp = g.node[close_edge[index].end].firstarc;
while (temp) {
if (close_edge[temp->adjvex].weight > temp->weight) {
close_edge[temp->adjvex].weight = temp->weight;
close_edge[temp->adjvex].start = index;
close_edge[temp->adjvex].end = temp->adjvex;
}
temp = temp->next;
}
}
}
int main()
{
Graph_List g;
createGraph(g);
print(g);
Prim(g, 1);
system("pause");
return 0;
克鲁斯卡算法
算法思路:
(1)将图中的所有边都去掉。
(2)将边按权值从小到大的顺序添加到图中,保证添加的过程中不会形成环
(3)重复上一步直到连接所有顶点,此时就生成了最小生成树。这是一种贪心策略。
克鲁斯卡算法——代码实现
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
bool check(int Vexnum,int edge) {
if (Vexnum <= 0 || edge <= 0 || ((Vexnum*(Vexnum - 1)) / 2) < edge)
return false;
return true;
}
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;
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;
}
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;
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;
}
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;
}