首先,写这个算法是为了了解“边集数组”的实战场景,而边集数组又是图的一种存储方式。
图大致分为下面几种表示方式,每种方式都有其应用场景:
1.邻接矩阵
2.邻接表
3.边集数组
4.链式前向星。
这四个方法后面记得整理一次,现在先记录克鲁斯卡尔算法(用到边集数组)
测试数据:
7 11
1 2 7
1 4 5
2 4 9
2 3 8
2 5 7
4 5 15
4 6 6
6 7 11
5 6 8
5 7 9
3 5 5
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn =1e5;
struct edge //边的数据类型
{
int u; //一条边的起始
int v; //结束
int w; //权值,这里可以看作是边的长度
bool operator <(const edge& a) //重载<运算符,这一步的目的是对边集合数组排序(排序的依据是权值大小,从小到大排序)
{
return w < a.w;
}
};
edge temp[maxn]; //存放边的数据
int verx[maxn];//辅助数组,判断是否连通。
edge tree[maxn];//最小生成树。
int n, m;//n*n的图,m条边。
int cnt;//统计生成结点个数,若不满足n个,则生成失败。
int sum;//最小生成树权值总和。
void print_tree() //输出最小生成树
{
cout << "最小生成树的权值是:" << endl;
cout << sum << endl;
for (int i = 0; i < cnt; i++)
{
cout << "顶点 " << tree[i].u << " 到顶点 " << tree[i].v << " 的边长是 " << tree[i].w << endl;
}
}
void Kruskal() //克鲁斯卡尔
{
for (int i = 1; i <= n; i++) //初始化辅助数组
verx[i] = i;
sum = 0;
cnt = 0;
for (int i = 0; i < m; i++)
{
int v1 = verx[temp[i].u];
int v2 = verx[temp[i].v];
if (v1 != v2) //这就是关键,不能形成回路
{
//先将这条满足条件的边加入到最小生成树里面
tree[cnt].u = v1;
tree[cnt].v = v2;
tree[cnt].w = temp[i].w;
//然后要收尾一下,把v1,v2设置为一个连通分量(即下次不会重复找该边)
for (int i = 1; i <= n; i++) //这里人为设置,v1,v2都变成v2
if (verx[i] == v2)verx[i] = v1;
sum += temp[i].w;
cnt++;
}
}
print_tree();
}
int main()
{
while (cin >> n >> m)
{
int u, v, w;
for (int i = 0; i < m; i++)
{
cin >> u >> v >> w;
temp[i].u = u, temp[i].v = v, temp[i].w = w;
}
sort(temp, temp + m);
Kruskal();
}
return 0;
}
//这个程序的目的在于3点:
//1.知道边集数组的概念,边集数组,就是存储边信息的数组,边的信息,无非就是起点终点,边的长度(也就是权值)
//2.知道克鲁斯卡尔算法的大致过程,即通过一个辅助数组verx,在不形成回路的情况下(v1!=v2),由已经从小到大排序的边里面,挑
//选当前最小的边,然后加入最小生成树,因为每次找到的边都是最小的,所以最后生成的树也是最小的,有点贪心的味道。
//3.结合起来,知道边集数组应用的场合