数据结构课设--7最小生成树问题

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


原文地址:https://www.cnblogs.com/lemonbiscuit/p/7776123.html