题目描述
题目:建造最小造价通信网问题
【基本功能】
设计一个10个结点的无向全连通网,代表10个不同的城市,结点之间的权值代表修建两座城市通信网花费的造价,以邻接矩阵方式存储。要求为该网设计一个建立通信网的优化算法,利用最小生成树技术生成网中的最小生成树,使得任意两个结点都能够互相连通,并且造价之和最小。
【基本要求】
(1)采用邻接矩阵作为存储结构。
(2)能够将最小生成树结果以树型结构显示出来。
(3)编写一个测试主函数,测试所实现的功能。
题目分析
题目要求采用邻接矩阵方式存储,邻接矩阵的结构体定义为:
typedef struct
{
SeqList Vertex;//顶点表
EdgeType edge[MaxVertex][MaxVertex];//邻接矩阵
int edgenum;//当前图中边的个数
}AdjMGraph;
由于顶点表本质上还是一个顺序表,因此对顶点表的插入,删除等操作可以调用顺序表的算法操作来实现。
要将最小生成树用树形结果打印出来,就得先将最小生成树转化为二叉树再来打印,其思想是在二叉树的二叉链表存储结构中增加一个标记位来判断每次最小生成树中结点应该插入的位置,二叉链表结构体定义为:
typedef struct Node
{
int data;
int counts;//用来标记应该插入左孩子或者右孩子,左0右1
struct Node* leftChild;
struct Node* rightChild;
}BiTreeNode;
当counts为0时,表示当前结点还可以插入,当counts为1时,表示当前结点已有元素,不可再插入。以在二叉树中插入左孩子为例,当一个结点插入到根结点的左孩子中时,将counts置为1,表示当前结点已经不能在插入,如果要再插入,就只能插入到根结点左孩子的左孩子中,或者根结点的右孩子中。
用该方法将最小生成树打印时,相当于限制了最小生成树的开始结点,在利用克鲁斯卡尔算法求解最小生成树时,已经限制了开始结点,所以后续结点与该结点相连,最终才能形成一个二叉树结构。
代码块
1、头文件seqlist.h
#include<stdio.h>
#define MaxSize 10
//顺序表结构体定义
typedef struct
{
int list[MaxSize];
int size;
}SeqList;
//初始化
void ListInitiate(SeqList* L)
{
L->size = 0;
}
//求表长
int Listsize(SeqList L)
{
return L.size;
}
//在顺序表中第i个位置插入值为x的元素
int ListInsert(SeqList* L, int i, int x)
{
if (L->size >= MaxSize)
{
printf("表已满无法插入!");
return 0;
}
else if (i<0 || i> MaxSize)
{
printf("参数i不合法!");
return 0;
}
else {
for (int j = L->size; j > i; j--)
{
L->list[j] = L->list[j - 1];
}
L->list[i] = x;
L->size++;
return 1;
}
}
//删除顺序表中第i的位置的元素,并将其赋给x返回
int ListDelete(SeqList* L, int i, int* x)
{
if (L->size <= 0)
{
printf("表已空无法删除!");
return 0;
}
else if (i<0 || i>L->size - 1)
{
printf("参数i不合法!");
return 0;
}
else
{
*x = L->list[i];
for (int j = i + 1; j <= L->size - 1; j++)
{
L->list[j - 1] = L->list[j];
}
L->size--;
return 1;
}
}
//取第i个位置的数据元素
int ListGet(SeqList L, int i, int* x)
{
if (i<0 || i>L.size - 1)
{
printf("参数i不合法");
return 0;
}
else
{
*x = L.list[i];
return 1;
}
}
2、头文件AdjMGraph
#pragma once
#include"SeqList.h"
#include<limits.h>
typedef char DataType;
typedef int EdgeType;
#define INFINITY 32767 //表示极大值∞
#define MaxVertex 10
typedef struct
{
SeqList Vertex;//顶点表
EdgeType edge[MaxVertex][MaxVertex];//邻接矩阵
int edgenum;//当前图中边的个数
}AdjMGraph;
//初始化
void GraphInitiate(AdjMGraph* G, int n)
{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (i == j)
G->edge[i][j] = 0;
else
G->edge[i][j] = INFINITY;
}
}
G->edgenum = 0;
ListInitiate(&G->Vertex);
}
//在顶点表在插入新的顶点,插在表的最后
void InsertVertex(AdjMGraph* G, DataType vertex)
{
ListInsert(&G->Vertex, G->Vertex.size, vertex);
}
//在邻接矩阵中插入元素,将权值赋给相邻的两个顶点,邻接矩阵中边的数量加1
void InsertEdge(AdjMGraph* G, int v1, int v2, int weight)
{
if (v1 < 0 || v1 >= G->Vertex.size || v2 < 0 || v2 >= G->Vertex.size)
{
printf("参数V1或者V2越界出错\n");
return;
}
G->edge[v1][v2] = weight;
G->edgenum++;
}
//删除邻接矩阵中的元素,将权值赋为无穷大,邻接矩阵中边的数量减1
void DeleteEdge(AdjMGraph* G, int v1, int v2)
{
if (v1 < 0 || v1 >= G->Vertex.size || v2 < 0 || v2 >= G->Vertex.size || v1 == v2)
{
printf("参数V1或者参数V2出错\n");
return;
}
if (G->edge[v1][v2] == INFINITY || v1 == v2)
{
printf("该边不存在\n");
return;
}
G->edge[v1][v2] = INFINITY;
G->edgenum--;
}
3、头文件AdjMGraphCreat
#include"AdjMGraph.h"
#include<stdio.h>
typedef struct
{
int row;
int col;
int weight;
}RowColWeight;//行列式,用来存储通信网的信息,各顶点以及之间的权值
void CreatGraph(AdjMGraph* G, DataType V[], int n, RowColWeight E[], int e)
{
int i, k;
GraphInitiate(G, n);
for (i = 0; i < n; i++)
{
InsertVertex(G, V[i]);
}
for (k = 0; k < e; k++)
{
InsertEdge(G, E[k].row, E[k].col, E[k].weight);
}
}
4、头文件BiTree
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef struct Node
{
int data;
int counts;//用来标记应该插入左孩子或者右孩子,左0右1
struct Node* leftChild;
struct Node* rightChild;
}BiTreeNode;
//初始化
void BiTreeInitiate(BiTreeNode** root)
{
*root = (BiTreeNode*)malloc(sizeof(BiTreeNode));
(*root)->leftChild = NULL;
(*root)->rightChild = NULL;
}
//将值为x的结点插入在根节点为root的树的左孩子
BiTreeNode* InsertLeftNode(BiTreeNode* root, int x)
{
BiTreeNode* s, * t;
if (root == NULL)
return NULL;
t = root->leftChild;
s = (BiTreeNode*)malloc(sizeof(BiTreeNode));
s->counts = 0;//将s的左孩子的标记记为0,代表下次插入必须插入要s左孩子的左孩子
s->data = x;
s->leftChild = t;
s->rightChild = NULL;
root->leftChild = s;
return root->leftChild;
}
//将值为x的结点插入在根节点为root的树的右孩子
BiTreeNode* InsertRightNode(BiTreeNode* root, int x)
{
BiTreeNode* s, * t;
if (root == NULL) return NULL;
t = root->rightChild;
s = (BiTreeNode*)malloc(sizeof(BiTreeNode));
s->counts = 0;//将新生成结点的counts置为0,表示s结点的左孩子不能插入,只能插入到右孩子
s->data = x;
s->rightChild = t;
s->leftChild = NULL;
root->rightChild = s;
return root->rightChild;
}
//打印二叉树
void PrintBiTree(BiTreeNode* root, int n)
{
int i;
if (root == NULL)
return;
PrintBiTree(root->rightChild, n + 1);
for (i = 0; i < n - 1; i++)
{
printf(" ");
}
if (n > 0)
{
printf("--");
printf("%c\n", root->data);
}
PrintBiTree(root->leftChild, n + 1);
}
//判断值为x的结点是否在root为根节点的树中
BiTreeNode* Search(BiTreeNode* root, int x)
{
BiTreeNode* find = NULL;
if (root != NULL)
{
if (root->data == x)
{
find = root;
}
else
{
find = Search(root->leftChild, x);
if (find == NULL)
find = Search(root->rightChild, x);
}
}
return find;
}
5、头文件kruskal
#include<stdio.h>
#include<stdlib.h>
#include"BTree.h"
#include"AdjMGraph.h"
typedef struct
{
int begin;
int end;
int weight;
}Edge;
//查找尾部下标
int Find(int* parent, int f)//判断是否属于同一个连通分量,从要插入的结点往前搜索,看前面结点是否有和该结点属于同一连通分量的
{
while (parent[f] > 0)
f = parent[f];
return f;
}
void Kruskal(AdjMGraph G)
{
Edge edges[100];//临时的结构体数组
int parent[10];
Edge newedges[9];//记录最小边
Edge temp;
int counts = 0;//记录最小边的个数
int flag = 0;//
int n, m;
BiTreeNode* root, * p;
BiTreeInitiate(&root);
p = InsertLeftNode(root, 65);
//将G.edge中的数据保存在新创建的结构体数组中
for (int i = 0; i < 10; i++) //创建temp邻接矩阵(中间变量) 因为原矩阵数据不能变
{
for (int j = 0; j < 10; j++)
{
edges[i * 10 + j].begin = i;
edges[i * 10 + j].end = j;
edges[i * 10 + j].weight = G.edge[i][j];
}
}
//冒泡排序实现边的权值排序
for (int i = 0; i < 99; i++)// 将begin作为排序,a为最大的冒泡排序:为了构造最小生成树,最小生成树是以a为根节点的,属于要先找到与a相关的所有节点
{
for (int j = 0; j < 99 - i; j++)
{
Edge max;
if (edges[j].weight > edges[j + 1].weight)
{
max = edges[j];
edges[j] = edges[j + 1];
edges[j + 1] = max;
}
}
}
//构建最小生成树并打印结果
for (int i = 0; i < 99; i++)
{
if (edges[i].weight != 0 && edges[i].weight != INFINITY) //打印连通分量里面的边
{
n = Find(parent, edges[i].begin);
m = Find(parent, edges[i].end);
if (n != m) //若该边的开始结点和终止结点不属于同一个连通分量
{
parent[n] = m;//将该边的终止结点m加入开始结点n的连通分量中
newedges[counts] = edges[i];//newedges[]为临时数组变量,用来存储MST中的每一条边
counts++;//最小生成边从0,1,2,3,4,5,6,7,8,9加入newedges[]数组中
}
}
}
//对MST中的最小边排序
for (int i = 0; i < 8; i++) //为了kruskal所以先冒泡排序,kruskal算法要求找最小边
{
for (int j = 0; j < 8 - i; j++)
{
Edge max;
if (newedges[j].begin > newedges[j + 1].begin)
{
max = newedges[j];
newedges[j] = newedges[j + 1];
newedges[j + 1] = max;
}
}
}
//为了打印最小生成树
for (int i = 0; i < 9; i++)
{
if (Search(root, newedges[i].begin + 65) != NULL)//判断要插入的结点是否在最小生成树中
{
p = Search(root, newedges[i].begin + 65);//判断开始结点是否在MST中
if (p->counts == 0)
{
InsertLeftNode(p, newedges[i].end + 65);//把与begin相邻的结点插入左孩子
p->counts++;//表示左孩子已经不能再插入
}
else if (p->counts == 1)
{
InsertRightNode(p, newedges[i].end + 65);
}
}
else if (Search(root, newedges[i].end + 65) != NULL) //判断终止结点是否在MST中
{
p = Search(root, newedges[i].end + 65);
if (p->counts == 0)
{
InsertLeftNode(p, newedges[i].begin + 65);
p->counts++;
}
else if (p->counts == 1)
{
InsertRightNode(root, newedges[i].begin + 65);
}
}
}
int sum=0;
for (int i = 0; i < 9; i++)
{
printf("结点%c到结点%c权值为%d\n", newedges[i].begin + 65, newedges[i].end + 65, newedges[i].weight);
}
for (int i = 0; i < 9; i++)
{
sum += newedges[i].weight;
}
printf("得到最小生成树的权值%d\n", sum);
PrintBiTree(root, 0);
}
6、源文件
#include<stdio.h>
#include"AdjMGraph.h"
#include"AdjMGrapCreat.h"
#include"Kruskal.h"
int main()
{
AdjMGraph G;
DataType a[] = {};
RowColWeight rcw[] = {};
int n = ;
int e = ;
int i, j;
CreatGraph(&G, a, 10, rcw, e);
printf("顶点集合为:\n");
for (i = 0; i < G.Vertex.size; i++)
{
printf("%c ", G.Vertex.list[i]);
}
printf("\n");
printf("邻接矩阵为:\n");
for (i = 0; i < G.Vertex.size; i++)
{
for (j = 0; j < G.Vertex.size; j++)
{
if (G.edge[i][j] == INFINITY)
printf("%3s", "∞");
else
printf("%3d", G.edge[i][j]);
}
printf("\n");
}
Kruskal(G);
}
DataType a[] = {};
RowColWeight rcw[] = {};
int n = ;
int e = ;
顶点集合,边集合,顶点个数和边的个数等数据需要根据实际的图来赋值。
运行结果