生成树和最小生成树有许多重要的应用
例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
什么是生成树?
如果一个无向连通图不包含回路(连通图中不存在环),
那么就是一个树。
什么是最小生成树?
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
最小生成树其实是最小权重生成树的简称。
Prime 普里姆算法
Prim算法每次循环都将一个蓝点(未访问点)u变为白点(已经访问点),并且此蓝点u与白点相连的最小边权min[u]还是当前所有蓝点中最小的。这样相当于向生成树中添加了n-1次最小的边,最后得到的一定是最小生成树。
Prime算法的**时间复杂度为o(n^2)**适合处理稠密图。
测试数据
3
6 10
v1 v2 v3 v4 v5 v6
v1 v2 6
v1 v3 1
v1 v4 5
v2 v3 5
v2 v5 3
v3 v5 6
v3 v6 4
v3 v4 5
v4 v6 2
v5 v6 6
测试输出
v1—v3
v3—v6
v6—v4
v3—v2
v2—v5
代码
#include<iostream>
#include<climits>
#include<string>
#include<cstdlib>
using namespace std;
#define ERROR -1
#define MAX_VERTEX_NUM 20 //最多顶点个数
#define INFINITY INT_MAX //最大值
typedef enum { DG, DN, UDG, UDN } GraphKind; //有向图、有向网、无向图、无向网
typedef int VRType, Status;
typedef string VertexType;
typedef struct ArcCell {
VRType adj; //VRType是顶点关系类型。对无权图用0
//表示相邻否;对带权图则为权值类型
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct {
VertexType vexs[MAX_VERTEX_NUM]; //顶点向量
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //图的当前顶点数和弧数
int kind;
}MGraph;
//根据映射定位下标
int LocateVex(MGraph G, VertexType v)
{
for (int i = 0; i < G.vexnum; i++) {
if (v == G.vexs[i]) return i;
}
return ERROR;
}
Status CreateDG(MGraph& G)//有向图
{
cout << "请输入顶点数和弧数:\n";
cin >> G.vexnum >> G.arcnum;
for (int i = 0; i < G.vexnum; i++) {
cout << "请输入向量信息:\n";
cin >> G.vexs[i];
}
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
G.arcs[i][j].adj = INFINITY;
}
}
VertexType v, u; // 出发点 目标点
for (int k = 0; k < G.arcnum; k++) {
cout << "请输入第" << k + 1 << "条弧的信息(v,u):\n";
cin >> v >> u;
int i = LocateVex(G, v);
int j = LocateVex(G, u);
G.arcs[i][j].adj = 1;
}
return 1;
}
Status CreateDN(MGraph& G)//有向网
{
cout << "请输入顶点数和弧数:\n";
cin >> G.vexnum >> G.arcnum;
for (int i = 0; i < G.vexnum; i++) {
cout << "请输入向量信息:\n";
cin >> G.vexs[i];
}
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
G.arcs[i][j].adj = INFINITY;
}
}
VertexType v, u; // 出发点 目标点
for (int k = 0; k < G.arcnum; k++) {
cout << "请输入第" << k + 1 << "条弧的信息(v,u):\n";
cin >> v >> u;
int i = LocateVex(G, v);
int j = LocateVex(G, u);
cout << "请输入包含的额外信息(边权):\n";
cin >> G.arcs[i][j].adj;
}
return 1;
}
Status CreateUDG(MGraph& G)//无向图
{
cout << "请输入顶点数和弧数:\n";
cin >> G.vexnum >> G.arcnum;
for (int i = 0; i < G.vexnum; i++) {
cout << "请输入向量信息:\n";
cin >> G.vexs[i];
}
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
G.arcs[i][j].adj = INFINITY;
}
}
VertexType v, u; // 出发点 目标点
for (int k = 0; k < G.arcnum; k++) {
cout << "请输入第" << k + 1 << "条弧的信息(v,u):\n";
cin >> v >> u;
int i = LocateVex(G, v);
int j = LocateVex(G, u);
G.arcs[i][j].adj = G.arcs[j][i].adj = 1;
}
return 1;
}
Status CreateUDN(MGraph& G)//无向网
{
cout << "请输入顶点数和弧数:\n";
cin >> G.vexnum >> G.arcnum;
for (int i = 0; i < G.vexnum; i++) {
cout << "请输入第" << i + 1 << "向量信息:\n";
cin >> G.vexs[i];
}
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
G.arcs[i][j].adj = INFINITY;
}
}
VertexType v, u; // 出发点 目标点
int w; // 权重
for (int k = 0; k < G.arcnum; k++) {
cout << "请输入第" << k + 1 << "条弧的信息(v,u):\n";
cin >> v >> u;
int i = LocateVex(G, v);
int j = LocateVex(G, u);
cout << "请输入包含的额外信息(边权):\n";
cin >> G.arcs[i][j].adj;
G.arcs[j][i].adj = G.arcs[i][j].adj;
}
return 1;
}
Status CreateGraph(MGraph& G)
{
cin >> G.kind;
switch (G.kind)
{
case DG:return CreateDG(G);
case DN:return CreateDN(G);
case UDG:return CreateUDG(G);
case UDN:return CreateUDN(G);
default: return ERROR;
}
}//CreateGraph
void TraveGraph(MGraph G)
{
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
cout << G.arcs[i][j].adj << ' ';
}
cout << '\n';
}
}
void Prime(MGraph G, VertexType u)
{
struct closedge{
VertexType adjvex;
VRType lowcost;
}closedge[MAX_VERTEX_NUM];
int k = LocateVex(G, u);
//辅助数组初始化
for (int i = 0; i < G.vexnum; i++) {
if (i != k) closedge[i] = { u,G.arcs[k][i].adj };
}
//初始
closedge[k].lowcost = 0;
for (int i = 1; i < G.vexnum; i++)//选择其余G.vexnum - 1个顶点
{
k = -1;
for (int j = 0; j < G.vexnum; j++) {//求出T的下一个结点:第k顶点
if (closedge[j].lowcost > 0 && (k == -1 || closedge[j].lowcost < closedge[k].lowcost)) k = j;
}
//输出生成树的边
cout << closedge[k].adjvex << "—" << G.vexs[k] << endl;
closedge[k].lowcost = 0;//第k顶点并入U集
for (int j = 0; j < G.vexnum; j++) {
if(G.arcs[k][j].adj<closedge[j].lowcost)
closedge[j] = { G.vexs[k],G.arcs[k][j].adj };
}
}
}
int main()
{
MGraph G;
cout << "请输入要创建的图的类型(有向图 0、有向网 1、无向图 2、无向网 3):\n";
CreateGraph(G);
//TraveGraph(G);
Prime(G, "v1");
return 0;
}
Kruskal 克鲁斯卡尔算法
Kruskal(克鲁斯卡尔)算法是一种巧妙利用并查集和贪心思想来求最小生成树的算法。平均时间复杂度为o(mlog m)。
Kruskal首先将所有的边按从小到大顺序排序,并认为每一个点都是孤立的,分属于n个独立的集合。然后按顺序枚举每一条边。如果这条边连接着两个不同的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合;如果这条边连接的两个点属于同一集合,就跳过。直到选取了n-1条边为止。
由于克鲁斯卡尔贪心思想,其生成树有特殊性质最大边权最小的生成树。
测试数据
6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 5 6
3 6 4
3 4 5
4 6 2
5 6 6
测试输出
1—3
4—6
2—5
3—6
2—3
代码
#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_VERTEX_NUM 20 //最多顶点个数
#define MAX_ARCNUM_NUM 20 //最多边个数
int fa[MAX_VERTEX_NUM];
//存储边
struct node {
int u, v, w;
bool operator<(const node& t)const {
return this->w < t.w;
}
}edge[MAX_ARCNUM_NUM];
//并查集模板
int find_fa(int x)
{
return fa[x] == x ? x : find_fa(fa[x]);
}
void init(int n)
{
for (int i = 1; i <= n; i++) fa[i] = i;
}
void _union(int x, int y)
{
int root_x = find_fa(x), root_y = find_fa(y);
if (root_x != root_y)
fa[root_x] = root_y;
}
int main()
{
//顶点数 边数
int vexnum, arcnum;
cin >> vexnum >> arcnum;
init(vexnum);
for (int i = 0; i < arcnum; i++) {
int u, v, w;
cin >> u >> v >> w;
edge[i] = { u,v,w };
}
//贪心:优先选取边权小的加入同一集合
sort(edge, edge + arcnum);
//vexnum个顶点的最小生成树最多有vexnum条边
int ans = vexnum - 1;
for (int i = 0; i < arcnum && ans; i++)
{
int root_u = find_fa(edge[i].u);
int root_v = find_fa(edge[i].v);
//在同一集合中退出!
if (root_u == root_v) continue;
//不在同一集合中合并!
fa[root_u] = root_v;
cout << edge[i].u << "—" << edge[i].v << endl;
ans--;
}
return 0;
}