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

1、Edge.h

#pragma
#ifndef
#define
//用邻接矩阵来存储
#define//最大顶点数
#define//无穷大
#define//认为较小
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

2、heapSort.h

#pragma
#ifndef
#define
#include<iostream>
#include<ctime>
#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]<r[2s],交换r[s]和r[2s],以r[2s+1]为根的子树是堆,而r[2s]为根的子树调整为堆,重复上述,直到叶子结点为止;
// 否则已经是堆,不用调整
//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

3、ConsoleApplication1.cpp.cpp

#include<iostream>
#include"heapSort.h"
#include<fstream>
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
*/