开头让我插入一个Github传送门:JZX555的Github
void Prim(Node Start) {
Node v, w; v = Start;
v is known;
while(have node not be retrieved) {
// 更新边信息
for each w adjacent to v
update information v--w;
// 寻找最小权重的边
find the least weighted edge v--w && w is unknown;
w is known;
}
}
看看伪代码其实还是很简单的吧。
2.Kruskal算法:
Kruskal算法的原理其实和Prim算法有很多类似的地方,不过与Prim不同,Kruskal算法是在所有的边中选取权重最小的不构成圈的边,直到选取的边数为节点数减一(即VexNum - 1),这样我们就可以构成一个最生成树了;其中我们需要注意的是如何判断一个节点是否与另外一个节点链接,这里我们就可以使用Union/Find算法进行判断;而对于最小边的选取,我们可以使用优先队列来完成。
注:Union/Find算法,即不相交集算法,用于判断元素是否在同一个集合中;其中Find(x)返回元素x所在的集合,Set(x, y)将x,y所在集合链接在一起。具体可以参考我的另外一篇博客哦~~
伪代码如下:
void Kruskal() {
int EdgeAccepted = 0;
PriorityQueue H;
Disjoint_Set S;
Node v, w;
SetType vSet, wSet;
Edge e;
Read Edges into H;
while(EdgeAccepted < VexNum) {
// 获取权重最小的边
E = DeleteMin(H);
vSet = S.Find(v), wSet = S.Find(w);
// 判断两个顶点是否链接
if(vSet != wSet) {
S.Set(v, w);
EdgeAccepted++;
}
}
}
最后,就是我们的完整代码了,首先是.h文件:
#ifndef ALGRAPH_H
#define ALGRAPH_H
#include
#include
#include
#include "Disjoint_Set.h"
using namespace std;
// 重定义边节点,便于操作
typedef struct ArcNode *Position;
/* 边节点
* 储存元素:
* AdjName:该有向边的尾结点
* Weight:该有向边的权重
* Next:该有向边的头结点的其他有向边
*/
struct ArcNode {
int AdjName;
int Weight;
Position Next;
};
/* 边节点:(用于KrusKal算法)
* 储存元素:
* Vex1:该无向边的节点
* Vex2:该无向边的另一个节点
* Weight:该无向边的权重
*/
struct Edge {
int Vex1;
int Vex2;
int Weight;
// 重载“<”符号,用于KrusKal算法
friend bool operator < (const Edge &a, const Edge &b) {
return a.Weight > b.Weight;
}
};
/* 顶点节点
* 储存元素:
* Name:该顶点的名称
* FirstArc:以该顶点为头结点的第一个有向边
*/
struct VexNode {
int Name;
Position FirstArc;
};
/* 表节点
* 储存元素:
* Known:该节点的状态
* Dist:该节点距指定节点的距离
* Path:指向该节点的节点
*/
struct TableNode {
bool Known;
int Dist;
int Path;
};
/* ALGraph类
* 接口:
* Display:展示功能,展示距离表中的信息
* MakeEmpty:重置功能,重置该图中的所有边
* Creat:构建功能,在图中构建边
* KrusKal:KrusKal算法,在图中寻找最小生成树
* Prim:Prim算法,在图中寻找最小生成树
*/
class ALGraph
{
public:
// 构造函数
ALGraph(int = 10);
// 析构函数
~ALGraph();
//--------- 接口函数------------
// 基础函数
void Display();
void MakeEmpty();
void Creat();
// 最小生成树函数
void KrusKal();
void Prim();
//-------------------------------
private:
// 辅助函数
void InitTable();
// 数据成员
int VexNum; // 储存顶点数
int ArcNum; // 储存边数
VexNode *AdjList; // 储存邻接表
TableNode *Table; // 储存距离表
priority_queue H; // 储存优先队列(用于KrusKal算法)
};
#endif
#include "stdafx.h"
#include "ALGraph.h"
/* 构造函数:初始化对象
* 返回值:无
* 参数:vnum:图中的顶点数
*/
ALGraph::ALGraph(int vnum)
: VexNum(vnum), ArcNum(0){
// 申请邻接表储存空间
AdjList = new VexNode[VexNum + 1];
// 申请距离表储存空间
Table = new TableNode[VexNum + 1];
// 判断是否申请成功
if (AdjList == NULL || Table == NULL) {
cout << "邻接表申请失败!" << endl;
return;
}
// 初始化邻接表以及距离表
for (int i = 0; i < VexNum + 1; i++) {
AdjList[i].FirstArc = NULL;
AdjList[i].Name = i;
Table[i].Dist = INT_MAX;
Table[i].Known = false;
Table[i].Path = 0;
}
}
/* 析构函数:对象消亡时回收储存空间
* 返回值:无
* 参数:无
*/
ALGraph::~ALGraph()
{
// 重置所有的边
MakeEmpty();
// 删除邻接表
delete AdjList;
AdjList = NULL;
// 删除距离表
delete Table;
Table = NULL;
}
/* 重置函数:重置所有的边
* 返回值:无
* 参数:无
*/
void ALGraph::MakeEmpty() {
// 暂时储存中间节点
Position P;
// 遍历邻接表
for (int i = 1; i < VexNum + 1; i++) {
P = AdjList[i].FirstArc;
// 遍历所有链接的边
while (P != NULL) {
AdjList[i].FirstArc = P->Next;
delete P;
P = AdjList[i].FirstArc;
}
}
// 边数初始化
ArcNum = 0;
}
/* 构建函数:在图中构建有向边
* 返回值:无
* 参数:无
*/
void ALGraph::Creat() {
int tmp; // 储存边数
cout << "请输入要创建的无向边数: ";
cin >> tmp;
ArcNum += tmp;
// 创建所有的无向边
for (int i = 0; i < tmp; i++) {
// v:边的顶点
// w:边的另一个顶点
// weight:边的权重
int v, w, weight;
cout << "请输入要创建的无向边(v, w): ";
cin >> v >> w;
cout << "请输入该无向边的权重: ";
cin >> weight;
// 构建一个单向边
Position P1 = new ArcNode();
P1->AdjName = w;
P1->Weight = weight;
P1->Next = AdjList[v].FirstArc;
AdjList[v].FirstArc = P1;
// 构建另一个单向边
Position P2 = new ArcNode();
P2->AdjName = v;
P2->Weight = weight;
P2->Next = AdjList[w].FirstArc;
AdjList[w].FirstArc = P2;
// 构建一个无向边(用于KrusKal算法)
Edge E;
E.Vex1 = v;
E.Vex2 = w;
E.Weight = weight;
// 将该无向边压入优先队列
H.push(E);
}
}
/* 初始化函数:初始化距离表
* 返回值:无
* 参数:无
*/
void ALGraph::InitTable() {
// 遍历所有的距离表
for (int i = 0; i < VexNum + 1; i++) {
// 初始化参数
Table[i].Dist = INT_MAX;
Table[i].Known = false;
Table[i].Path = 0;
}
}
/* 展示函数:展示距离表中的信息
* 返回值:无
* 参数:无
*/
void ALGraph::Display() {
// 遍历所有的距离表
for (int i = 1; i < VexNum + 1; i++) {
cout << "节点: " << i << " , 距离: " << Table[i].Dist << " , 路径: " << Table[i].Path << endl;
}
}
/* KrusKal算法:寻找最小生成树
* 返回值:无
* 参数:无
*/
void ALGraph::KrusKal() {
// S:不相交集,用于判断两个顶点是否链接
// v:一条无向边的顶点
// w:一条无向边的另一个顶点
// EdgeAccepted:已经生成的边数
// V:顶点v所在的集合
// W:顶点w所在的集合
// E:无向边节点
Disjoint_Set S(VexNum);
int v, w, EdgeAccepted = 0;
SetType V, W;
Edge E;
// 初始化距离表
InitTable();
// 一直寻找直到生成的边数为:VexNum - 1
while (EdgeAccepted < VexNum - 1) {
// 获取最小的无向边
E = H.top();
H.pop();
// 获取无向边的顶点
v = E.Vex1;
w = E.Vex2;
// 获取顶点所在的集合
V = S.Find(v);
W = S.Find(w);
// 判断两个顶点是否在同一个集合
if (V != W) {
// 生成边数增加
EdgeAccepted++;
// 链接量顶点
S.SetUnion(V, W);
// 输出信息
cout << "顶点1: " << E.Vex1 << " , 顶点2: " << E.Vex2 << " , 距离: " << E.Weight << endl;;
}
}
}
/* Prim算法:寻找最小生成树
* 返回值:无
* 参数:无
*/
void ALGraph::Prim() {
// v:一条无向边的顶点
// w:一条无向边的另一个顶点
// counter:计数器,统计边数
// P:储存边节点
int v, w, counter;
Position P;
// 初始化距离表,并选取起始节点
InitTable();
v = 1;
counter = 1;
Table[v].Dist = 0;
Table[v].Known = true;
// 一直寻找,直到边数为:VexNum - 1
while (counter != VexNum) {
// 获取边节点
P = AdjList[v].FirstArc;
// 遍历完该节点所有无向边
while (P != NULL) {
w = P->AdjName;
// 判断是否需要更新距离表信息
if (Table[w].Known == false && Table[w].Dist > P->Weight) {
Table[w].Dist = P->Weight;
Table[w].Path = v;
}
// 指向下一个无向边
P = P->Next;
}
// 寻找不构成圈的权重最小的无向边
int Min = INT_MAX;
for (int i = 1; i < VexNum + 1; i++) {
if (Table[i].Known == false && Min > Table[i].Dist) {
v = i;
Min = Table[i].Dist;
}
}
// 链接该无向边,计数器增加
Table[v].Known = true;
counter++;
}
}
参考文献:《数据结构与算法分析——C语言描述》