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;
}