最小生成树问题:若要在n个城市之间建设通讯网络,只需要架设n-1条线路即可(隔壁学校同学的课设)

题目8 最小生成树问题(难度系数:1.1)
[问题描述]
若要在n个城市之间建设通讯网络,只需要架设n-1条线路即可。如何以最低的经济代价建设这个通讯网,是一个网的最小生成树问题。
[基本要求]
(1)利用克鲁斯卡尔算法求网的最小生成树。
(2)实现并查集。以此表示构造生成树过程中的连通分量。
(3)以文本形式输出生成树中各条边以及他们的权值。
[测试数据]
参见本题集中的习题。
[实现提示]
通讯线路一旦建立,必然是双向的。因此,构造最小生成树的网一定是无向网。设图的顶点数不超过30个,并为简单起见,网中边的权值设成小于100的整数,可利用C语言提供的随机数函数产生。
图的存储结构的选取应和所作操作向适应。为了便于选择权值最小的边,此题的存储结构既不选用邻接矩阵的数组表示法,也不选用邻接表,而是以存储边(带权)的数组表示图。
[选做内容]
利用堆排序实现选择权值最小的边。

#pragma once
#ifndef EDGE
#define EDGE
//用邻接矩阵来存储
#define MaxNum 30//最大顶点数
#define MaxInt 32767//无穷大
#define MinInt -1//认为较小
typedef char  VerType;//数据类型
typedef int ArcType;//权值类型
//定义图的结构
typedef struct {
	VerType ver[MaxNum];//顶点表
	ArcType arc[MaxNum][MaxNum];//邻接矩阵
	int vernum, arcnum;	//点数,边数
}AMGraph;
//引入辅助结构1,Edge:储存边的信息,包括两个点的信息和权值
struct edge {
	VerType Head;//边的起点
	VerType Tail;//边的终点
	ArcType lowCost;//边的权值
}Edge[MaxNum+1];//边的信息
//有n个顶点的强连通图最多有n(n-1)条边,最少有n条边,所以这里最多考虑为31,从1-30存储边
//定位
int Locate(AMGraph G, VerType v1) {
	for (int i = 0; i < G.vernum; i++) {
		if (G.ver[i] == v1) {
			return i;
		}
	}
	return -1;
}
#endif
#pragma once
#ifndef HEAPSORT
#define HEAPSORT
#include
#include
#include"Edge.h"
//大根堆-》小根堆
using namespace std;
void show_heapSort_result(edge l[], int n) {
	for (int i = 1; i <=n; i++) {
		cout << l[i].lowCost << " ";
	}
	cout << endl;
}
// Heap Sort是树形选择排序,r[n]是一个完全二叉树的顺序存储结构,利用完全二叉树双亲与孩子的关系,选择关键字最大或最小记录
//堆定义:1、Ki>=k(2i) &&Ki>=k(2i+1)或2、ki<=k(2i) &&Ki<=k(2i+1)
//1、筛选法创建堆
//步骤:1.r[2s]和r[2s+1]中选择关键字最大的,假如r[2s+1]小,就比较r[s]和r[2s]的关键字
void heapAdjust(edge L[],int s,int m) {
	//假设r[s+1,..m]是堆,把r[s,..m]调整为以r[s]为根的大根堆
	edge rc = L[s];
	for (int j = 2 * s; j <= m; j *= 2) {//从N/2、N/2-1……开始,对其不是堆且较大的左右子树循环下去
		if (j < m && L[j].lowCost < L[j + 1].lowCost ) ++j;//比较.r[2s]和r[2s+1]
		if (rc.lowCost >= L[j].lowCost) {
			break;
		}//已经是堆,不用调整
		else
			L[s] = L[j]; 
			s = j;//调整r[2s]或r[2s+1]的子树
	}
	L[s] = rc;
}
//2.判断,如果r[s]
//			否则已经是堆,不用调整
//2.建初始堆,总的比较次数小等于4n
//完全二叉树中序号大于n/2(向下取整)的结点都是种子,已是堆
//把前面的调整为堆,就可以了
void CreateHeap(edge L[],int length) {
	//大根堆的建成
	for (int i = length / 2; i > 0; --i) {
		heapAdjust(L,i ,length );
	}
}
//算法实现
//通过反复进行交换和堆调整,(n-1)次筛选
void HeapSort(edge L[], int length) {
	CreateHeap(L, length);
	//int time = 1;
	for (int i = length; i > 1; --i) {
		edge x = L[1];
		L[1] = L[i]; 
		L[i] = x;
		heapAdjust(L, 1, i - 1);
		//printf("第%2d次调整\t", time++);
		//show_heapSort_result(L, length);
	}
}
//算法分析
/*
时间复杂度:最坏情况O(nlog2n)
空间复杂度o(1)
特点:不稳定;只能顺序结构;记录较少时不适合,当记录较多时,为高效
*/
#endif


#include
#include"heapSort.h"
#include
using namespace std;
//创建无向图
/*
通讯线路一旦建立,必然是双向的。
因此,构造最小生成树的网一定是无向网。
设图的顶点数不超过30个,并为简单起见,网中边的权值设成小于100的整数,可利用C语言提供的随机数函数产生。
*/
bool createUDN(AMGraph &G) {
	cout << "Please input the vernum and arcnum(点数和边数)" << endl;//点数和边数
	cin >> G.vernum >> G.arcnum;
	cout << "Please input the name of "<<G.vernum<<" points" << endl;
	int i = 0, j = 0;
	for (i = 0; i < G.vernum; i++)
		cin >> G.ver[i];
	//初始化邻接矩阵
	for (i = 0; i < G.vernum; i++) {
		for (j = 0; j < G.vernum; j++) {
			if (i != j)
				G.arc[i][j] = MaxInt;
			else 
				G.arc[i][j] = 0;
		}
	}
	//开始操作,构造邻接表
	cout << "Please input the name of two arcnums and end with line feeds at a time,without inputing a random value as their distance"<< endl;
	VerType v1, v2;
	ArcType w;
	int p, q;
	srand((unsigned)time(NULL));
	for (i = 1; i <= G.arcnum; i++) {//为了适应堆排序,第一位不用
		cin >> v1 >> v2; //>> w;
		w = rand() % 100+1;//从1-100
		p = Locate(G, v1);
		q = Locate(G, v2);
		Edge[i] = { v1,v2,w };
		G.arc[p][q] = w;
		G.arc[q][p] = G.arc[p][q];
	}
	return true;
}
void show_Graph(AMGraph G) {
	int i, j;
	for (i = 0; i < G.vernum; i++) {
		for (j = 0; j < G.vernum; j++) {
			if (G.arc[i][j] == MaxInt)  printf("INF\t");
			else printf("%-3d\t", G.arc[i][j]);
		}
		cout << endl;
	}
}
//算法思想:加边法
//			1.将数组Edge的元素从小到大排序
//			2.依次查看数组的边,循环下列操作:
//				2.1依次从排好序的Edge数组中选择一条边(u,u2);
//				2.2在Vexset中分别查找v1,v2所在的联通分量vs1,vs2,进行判断
//					如果vs1==vs2,表明2个顶点属于同一个联通分量,舍去此边,而选择下一条权值最小的边
//					vs1!=vs2,表明2个顶点属于不同两个联通分量,输出此边,合并两个联通分量
//引入辅助结构2,Vexset[i] 标识各个顶点所属的连通分量,初始化时都各自成为一个连通分量
//算法开始
void MiniSpanTree_Kruskal(AMGraph G) {
	//无向图以邻接矩阵形式存储,构造G的最小生成树,输出T的各边
	//初始化
	int Vexset[MaxNum];//点标记数组
	HeapSort(Edge, G.arcnum);//Edge从1-length
	int i = 0;
	for (i = 0; i < G.arcnum; i++)
		Vexset[i] = i;
	int v1, v2;//下标
	int vs1, vs2;//连通分量
	//开始
	ofstream outf;
	outf.open("C:\\Users\\Lenovo\\Desktop\\EdegeGraph.txt", ios::out);
	if (!outf) {
		cerr << "File couldn't be open" << endl;
		abort();
	}
	for (i = 1; i <= G.arcnum; i++) {
		v1 = Locate(G, Edge[i].Head);//Head下标
		v2 = Locate(G, Edge[i].Tail);//Tail下标
		vs1 = Vexset[v1];
		vs2 = Vexset[v2];
		if (vs1 != vs2) {
			cout << "start at point " << Edge[i].Head << " ,end at point " << Edge[i].Tail <<" value=" << Edge[i].lowCost << "\n";//输出此边
			outf << "start at point " << Edge[i].Head << " ,end at point " << Edge[i].Tail<<" value="<<Edge[i].lowCost<< "\n";//输出此边
			//合并两个联通分量
			for (int j = 0; j < G.vernum; j++) {//合并vs1和vs2两个分量,统一编号为vs1,相当于是归到一个连通分量
				if (Vexset[j] == vs2)
					Vexset[j] = vs1;//集合编号为vs2的都改成vs1
			}
		}
		//如果vs1==vs2,表明2个顶点属于同一个联通分量,舍去此边,而选择下一条权值最小的边,跳到下一次循环
	}
	outf.close();
}

int main() {
	AMGraph G;
	createUDN(G);
	show_Graph(G);
	MiniSpanTree_Kruskal(G);
}
/*
测试案例:
7 9
a b c d e f g
a b 
a f 
b c 
b g 
f e 
e g 
e d 
c d
d g 
*/

你可能感兴趣的:(c++数据结构)