14、最小生成树问题(**)
【问题描述】
若要在n个城市之间建设通信网络,只需要假设n-1条线路即可。如何以最低的经济代价建设这个通信网,是一个网的最小生成树问题。
【系统要求】
1.利用克鲁斯卡尔算法求网的最小生成树。
2.利用普里姆算法求网的最小生成树。
3.要求输出各条边及它们的权值。
【测试数据】
由学生任意指定,但报告上要求写出多批数据测试结果。
【实现提示】
通信线路一旦建成,必然是双向的。因此,构造最小生成树的网一定是无向网。设图的顶点数不超过30个,并为简单起见,网中边的权值设成小于100的整数,可利用C语言提供的随机函数产生。
图的存储结构的选取应和所作操作相适应。为了便于选择权值最小的边,此题的存储结构既不选用邻接矩阵的数组表示法,也不选用邻接表,而是以存储边(带权)的数组表示图。
【选作内容】
利用堆排序实现选择权值最小的边。
一、算法设计
1.随机生成图我利用的是C语言中的random()和rang()函数,采取系统时间为随机种子,再生成随机数,保证每次生成的图不相同。因为我所构造的图的存储是30*30的邻接矩阵,在构造图的过程中利用随机函数先产生1~30之间的30个随机数。然后从第一个数开始,判断之后的数是否与它相同,如果不同就再利用随机函数生成一个1~90之间的随机数作为边的权值。而第一个数作为边的始点,第二个数作为边的终点,保存进邻接矩阵。当然这不能够保证生成的图是联通的。所以对邻接矩阵进行一次遍历。如果该行所有数全部为0,就产生一个与此时行数不同的1~30之间的数,一个1~90之间的数,重新进行保存,保证生成的图肯定是一个连通图。然后在进行一趟遍历,统计总共有的边数,同时遍历的过程检查是否为无向图,如果不是,行列互换位置赋值,保证是无向图。边集数组构造好之后需要按照权重大小进行排序,这里我利用的是快速排序,与题目的中的堆排序有些出入。边集数组是为克鲁斯卡尔算法做准备的,每次加入一条边检查是否属于构成了环,所以还需要利用一个辅助数组来标记,记为father数组,类似于并查集中的思想。对于普里姆算法,需要将原来的邻接矩阵重新修整,所有为0的地方全部变为INF(即假定的无穷大数值)。在最后计算总花费的时候再利用原来的邻接矩阵计算。
各个函数之间的调用关系如下图所示:
main() |
||||
mainjiemian() |
||||
CreatGraph() |
prim() |
quicksort() |
kruskal() |
jieshu() |
partion() |
find() |
|||
mainjiemian() |
2.本程序中包含8个模块
(1)主函数:int main();
(2)结束函数:void jieshu();
(3)主界面函数:void mainjiemian();
(4)克鲁斯卡尔算法:void kruskal(Edgetype edge[MAX_EDGE_NUM]);
(5)快速排序函数:
void quicksort(Edgetype edge[MAX_EDGE_NUM],int low, int high)
int partion(Edgetype edge[MAX_EDGE_NUM], intlow, int high)
(6)寻找根结点:int find(intfather[MaxVertexnum], int v);
(7)普里姆算法:void prim(Graph*G,int w[][100], int fa[], int n);
(8)随机生成图:voidCreatGraph(Graph *G);
3.元素类型、结点类型和指针类型
#define INF 65535
#define MaxVertexnum 35 /*最大顶点数为30*/
#define MAX_EDGE_NUM 1000
typedef int VertexType; /*顶点类型设置为字符型*/
typedef int EdgeType; /*边的权值设为整型*/
typedef struct
{
VertexTypevexs[MaxVertexnum]; /*顶点表*/
EdgeTypeedges[MaxVertexnum][MaxVertexnum]; /*邻接矩阵,即边表*/
int n, e; /*顶点数和边数*/
}Graph; /*Graph是以邻接矩阵存储的图类型*/
typedef struct
{
int v1;
int v2;
int cost;
}Edgetype;
二、实验测试
图的生成并不是固定的随机方法,可以保证前后无关联性
源代码:
#include<iostream> #include<stdio.h> #include<algorithm> #include<stack> #include<queue> #include<vector> #include<string> #include<stdlib.h> #include<math.h> #include<iomanip> #include<time.h> using namespace std; int edgenum = 0; #define INF 65535 #define MaxVertexnum 35 /*最大顶点数为30*/ #define MAX_EDGE_NUM 1000 typedef int VertexType; /*顶点类型设置为字符型*/ typedef int EdgeType; /*边的权值设为整型*/ typedef struct { VertexType vexs[MaxVertexnum]; /*顶点表*/ EdgeType edges[MaxVertexnum][MaxVertexnum]; /*邻接矩阵,即边表*/ int n, e; /*顶点数和边数*/ }Graph; /*Graph是以邻接矩阵存储的图类型*/ typedef struct { int v1; int v2; int cost; }Edgetype; Edgetype edge[MAX_EDGE_NUM]; /*定义边集数组*/ void CreatGraph(Graph *G) /*建立无向图G的邻接矩阵存储*/ { int i, j; int ans[30]; G->n = 30; printf("随机生成无向图 "); srand((unsigned)time(NULL)); for (i = 0; i < MaxVertexnum; i++) /*初始化邻接矩阵*/ { for (j = 0; j < MaxVertexnum; j++) { G->edges[i][j] = 0; } } for (i = 0; i < 30; i++) ans[i] = rand() % 30 + 1; /*产生随机的城市*/ for (i = 0; i < 30; i++) /*随机生成权值,构造邻接矩阵*/ { for (j = i + 1; j < 30; j++) { if (ans[i] != ans[j]) { G->edges[ans[i]][ans[j]] = rand() % 90 + 1; G->edges[ans[j]][ans[i]] = G->edges[ans[i]][ans[j]]; } } } for (i = 1; i <= G->n; i++)/*完善整个图,使之必定为连通图*/ { int flag = 0; for (j = 1; j <= G->n; j++) { if ((G->edges[i][j]!= 0)&&i!=j) { flag = 1; break; } } if (!flag) { int t = i; while (t == i) { t = rand() % 30 + 1; } int v = rand() % 90 + 1; G->edges[i][t] = v; G->edges[t][i] = v; } } for (i = 1; i <= 30; i++) { for (j = 1; j <=30; j++) { if (G->edges[i][j] != 0) edgenum++; } } i = 1; while (i <= edgenum) { for (j = 1; j <= 30; j++) { for (int k = 1; k <= 30; k++) { if (G->edges[j][k] != 0) { edge[i].v1 = j; edge[i].v2 = k; edge[i].cost = G->edges[j][k]; /*边集数组同事也赋值*/ i++; } } } } for (i = 1; i <= G->n; i++) { for (j = 1; j <= G->n; j++) { cout << std::left << setw(4) << G->edges[i][j]; } printf(" "); } } void prim(Graph *G,int w[][100], int fa[], int n) { int i, j, m, k; int d[100]; /*d[j]可以理解成结点j到生成树(集合)的距离,它的最终值是w[j][fa[j]]*/ for (j = 1; j <= n; j++) { d[j] = (j == 1 ? 0 : w[1][j]); /*将第一个结点加入集合,并初始化集合与其他结点的距离*/ fa[j] = 1; /*当前集合中有且只有一个结点1,其他结点暂时未加入集合,所以没有父结点,就先姑且初始化成1*/ } for (i = 2; i <= n; i++) { m = INF; for (j = 1; j <= n; j++) if (d[j] <= m && d[j] != 0) m = d[k = j]; /*选取与集合距离最小的边*/ d[k] = 0; /*0在这里表示与集合没有距离,也就是说赋值0就是将结点k添加到集合中*/ for (j = 1; j <= n; j++) /*对刚加入的结点k进行扫描,更新d[j]的值*/ if (d[j] > w[k][j] && d[j] != 0) { d[j] = w[k][j]; fa[j] = k; } } printf("最小生成树如下: "); int cost = 0; for (int i = 2; i <= 30; i++) /*打印结果*/ { printf("(%d,%d) ", i, fa[i]); cost += G->edges[i][fa[i]]; } printf("最少花费成本为:%d ", cost); } int find(int father[MaxVertexnum], int v)/*寻找顶点v所在树的根节点*/ { while (father[v] >= 0) { v = father[v]; } return v; } int partion(Edgetype edge[MAX_EDGE_NUM], int low, int high)/*对边集数组进行排序*/ { int i, j; Edgetype t; i = low; j = high; t = edge[low]; while (i<j) { while (i<j && edge[j].cost>t.cost) j--; if (i<j) edge[i++] = edge[j]; while (i<j && edge[i].cost <= t.cost) i++; if (i<j) edge[j--] = edge[i]; } edge[i] = t; return i; } void quicksort(Edgetype edge[MAX_EDGE_NUM], int low, int high) { int i; if (low < high) { i = partion(edge, low, high); quicksort(edge, low, i - 1); quicksort(edge, i + 1, high); } } void kruskal(Edgetype edge[MAX_EDGE_NUM]) { int i = 0, vf1, vf2, sum_cost = 0; int father[MaxVertexnum];/*用于辅助判断点顶点是否处于同一连通分量*/ /*添加边集数组edges并按照权值由小到大排序*/ for (i = 1; i <= 30; i++) father[i] = -1; printf("最小生成树如下: "); for (i = 1; i <= edgenum; i++)/*按照权值由小到大的顺序查找符合条件的边*/ { vf1 = find(father, edge[i].v1);/*vf1是v1所在树的根节点*/ vf2 = find(father, edge[i].v2);/*vf2是v1所在树的根节点*/ if (vf1 != vf2) /*根节点不同,说明v1和v2不在同一连通分量上*/ { father[vf2] = vf1; /*通过边(v1,v2)连接两个分量*/ printf("(%d,%d) ", edge[i].v1, edge[i].v2); sum_cost += edge[i].cost; } } printf("最少花费成本为:%d ", sum_cost); } void mainjiemian() { cout << " ★-----★---------★---------★-----★" << endl; cout << endl; cout << " ☆ 1 生成随机图 ☆" << endl; cout << " 2 普里姆算法 " << endl; cout << " ☆ 3 克鲁斯卡尔算法 ☆" << endl; cout << " 4 退出 " << endl; cout << " ★-----★---------★---------★-----★" << endl; cout << endl; cout << endl; cout << "请选择数字命令:"; } void jieshu()//结束显示 { cout << " ★-----★---------★---------★-----★" << endl; cout << endl; cout << " ☆ 感谢您的使用! ☆" << endl; cout << endl; cout << " ★-----★---------★---------★-----★" << endl; cout << endl; } int main() { system("color 57"); char*end;/*末端指针*/ string order; mainjiemian(); Graph *G = new Graph; while (cin >> order) { int a_order = static_cast<int>(strtol(order.c_str(), &end, 10));/*将输入进来的值转化为int类型*/ switch (a_order + 48) { case'1': { system("cls"); CreatGraph(G); system("pause"); system("cls"); mainjiemian(); break; } case'2': { system("cls"); int fa[MaxVertexnum]; int w[100][100]; for (int i = 1; i <= G->n; i++) { for (int j = 1; j <= G->n; j++) { w[i][j] = (G->edges[i][j] == 0) ? INF : G->edges[i][j]; } } prim(G, w, fa, G->n); system("pause"); system("cls"); mainjiemian(); break; } case'3': { system("cls"); quicksort(edge, 1, edgenum);/*边集数组按照权重由小到大排序*/ kruskal(edge); system("pause"); system("cls"); mainjiemian(); break; } case'4': { system("cls"); jieshu(); return 0; break; } default: { cin.clear(); cin.sync(); cout << "输入错误,重新返回主界面。" << endl; system("pause"); system("cls"); mainjiemian(); break; } } } return 0; }