最小生成树Prim 和Kruscal 算法的自动选择
实验目的
根据两种算法各自的特点,设计一个能够根据输入图的规模自动选择最优的最小生成树算法,并给出最小生成树的程序。
实验要求
- 编程实现求带权无向图最小生成树的Prim算法和Kruscal算法。
- 采取适当的数据结构储存用户输入的图。
- 根据图的特点选择性能更好的Prim算法或Kruscal算法输出该图的最小生成树。
概要设计
- 主程序使用邻接矩阵储存无向图,maxn规定图的顶点数的最大值,存图的矩阵使用全局变量定义,方便各算法的调用。
- 主程序首先给出用户提示“Prim算法和Kruscal算法的自动选择”,接着调用build_graph( )函数接收用户输入的数据并建图。
- 接下来的if判断语句是本程序实现自动选择的关键。若m < n*log(n),即图为边稀疏图,给出说明后调用Kruscal()函数,否则为边稠密图,调用Prim()函数。
- 注意以上两个算法都没有传入参数是因为有关图的基本信息皆使用全局变量定义,这样虽然使得程序之间高度耦合,但是避免了形参实参的传递,能够提升程序的执行效率。
- Prim算法中使用closedge[maxn]结构体数组来记录每个顶点到当前最小生成树的最短距离以及他的连接点,使用U[maxn]数组来记录顶点是否已经在MST中。核心步骤是每次选择到当前MST估计距离最短的点加入集合,并将此最短边加入最小生成树,之后松弛此点的各个邻接点。
- Kruscal算法的实现主要用到了并查集和优先队列。优先队列的内部用堆实现了每次权重最小边的查找,并查集的使用保证每次新加入的都将森林中的两棵树进行合并,而不是在一棵树内部形成回路,这样就维护了最小生成树的性质。核心步骤是每次选择权重最小的“桥”来合并两棵树,直到选够n-1条边。
- 并查集中主要用到路径压缩和合并两个操作,union()函数是将连接的两棵树归属到同一个并查集中,以便下次判断;findroot()函数是寻找给定节点的根进行判断,并在找到根节点后进行路径压缩,以提高下次查询的效率。
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<queue>
using namespace std;
const int inf = 0x7fffffff;
const int maxn = 101;//最大顶点数
int gra[maxn][maxn];//全局变量定义图的基本信息
int n,m;
void build_graph(void)//建图
{
cout << "输入顶点数n和边数m\n";
cin >> n>>m;
cout << "输入m条边的起点终点和权重\nv1->v2:weight\n";
int v1, v2, ww;
for (int k = 1; k <= m; ++k) {
cin >> v1 >> v2 >> ww;
gra[v1][v2] = gra[v2][v1] = ww;//邻接矩阵存图
}
}
void Prim() //MST_Prim algorithms
{
struct
{
int vex;
int w;
} closedge[maxn];//存节点最短距离的结构体数组
int U[maxn] = { 0 }; //标记节点是否已经在MST中
U[1] = 1;
for (int i = 2; i <= n; ++i) //从节点1 开始
{
if (gra[1][i] == 0)
closedge[i].w = inf;
else
{
closedge[i].w = gra[1][i];
closedge[i].vex = 1;
}
}
cout << "Minimal Spanning Tree_Prim:\nedge:\tweight\n";
for (int i = 1; i < n; ++i)//n-1次循环选择n-1个顶点加入MST
{
int km = 0, mine = inf;
for (int j = 1; j <= n; ++j) //贪心选择最近的未在MST中的点,o(n)
{
if (closedge[j].w < mine&&U[j] == 0)
{
mine = closedge[j].w;//记录最短距离
km = j;//记录连接点下标
}
}
U[km] = 1;
cout << closedge[km].vex << '-' << km << ":\t" << closedge[km].w << endl;
for (int j = 1; j <= n; ++j) //松弛相连各边
{
if (U[j] == 0 && gra[km][j] != 0)
if (gra[km][j] < closedge[j].w)
{
closedge[j].vex = km;
closedge[j].w = gra[km][j];
}
}
}
}
struct EV//定义边结构体
{
int w, v1, v2;
EV(int a1, int a2, int a3)
{
w = a1, v1 = a2, v2 = a3;
}
};
struct cmp //重载运算符()
{
bool operator()(const EV a, const EV b)
{
return a.w > b.w;
}
};
int parent[maxn];//父节点
int findroot(int v1) //寻找根节点并进行路径压缩
{
int p;
for (p = v1; parent[p] != -1; p = parent[p]); //先找到根节点
int q = v1;
while (q != p)//路径压缩
{
q = parent[v1];
parent[v1] = p;
v1 = q;
}
return v1;
}
void unionv(int v1, int v2) //合并两个连通分量,下次findroot时会路径压缩
{
int r1 = findroot(v1), r2 = findroot(v2);
parent[r2] = r1;
}
void Kruscal() //MST_Kruscal algorithms//并查集,优先队列实现
{
memset(parent, 0, sizeof(parent));
priority_queue < EV, vector <EV>, cmp> que;
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n; ++j)
if (gra[i][j])
que.push(EV(gra[i][j], i, j));//优先队列实现取权重最小的边
for (int i = 1; i <= n; ++i)
parent[i] = -1;//初始化每个顶点都是一棵独立的树
cout << "Minimal Spanning Tree_Kruscal:\nweight:\tedge\n";
int cnt = n - 1;
while (cnt > 0)
{
struct EV t = que.top();//通过优先队列取当前权重最小的边
que.pop();
int r1 , r2 ;
if (findroot(t.v1)!= findroot(t.v2))//并查集查询
{
cout << t.w << '\t' << t.v1 << '-' << t.v2 << endl;
unionv(t.v1, t.v2);//合并
cnt--;
}
}
}
int main()
{
cout << "\n****MST:Prim算法和Kruskal算法的自动选用****\n" << endl;
build_graph();//建图,邻接矩阵存储
if (m < n*log(n))
{
cout << "***边稀疏图,选择Kruscal算法***\n";
Kruscal();
cout << endl;
}
else
{
cout << "***边稠密图,选择Prim算法***\n";
Prim();
cout << endl;
}
return 0;
}
/*测试用图
稠密图测试
6 13
1 2 6
1 3 1
1 4 5
1 5 7
2 3 5
2 4 7
2 5 3
3 4 5
3 5 6
3 6 4
4 5 2
4 6 2
5 6 6
稀疏图测试
6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6
*/