目录
阅读建议:
一、实验目的
二、实验内容
三、实验过程
四、代码结构
五、测试结果
阅读建议:
1.实验的软硬件环境要求:
(1)硬件环境要求:PC机
(2)软件环境要求:Windows 环境下的 Microsoft Visual Studio2.该实验采用了头文件(.h)和源文件(.cpp)相结合的形式。
1.熟练掌握图的邻接矩阵存储结构的实现;
2.熟练掌握图的基本操作算法实现,包括创建、遍历、连通性判断、最小生成树的求解等;
3.灵活使用图来解决具体的问题。
1.用邻接矩阵实现无向网的存储,封装图的基本操作算法,包括:
(1)创建
(2)顶点和边的增删、修改
(3)深度优先和广度优先遍历;
2.判断图的连通性,不连通则计算连通分量的个数;
3.实现Prim算法。
1.全局数组变量
const int MaxSize = 10;
int visited[MaxSize] = { 0 }; // 全局数组变量 visited 初始化
2.构造函数---图的建立,接受一个顶点数组 a
,顶点数量 n
和边数量 e
作为输入,然后使用这些信息初始化一个图的邻接矩阵。
工作原理:
- 首先,它根据输入的顶点和边数量初始化一些变量和数组。
- 然后,它使用输入的顶点数组初始化顶点数组。
- 接下来,它初始化邻接矩阵为全零,表示开始时没有边。
- 最后,它读取边的信息,并更新邻接矩阵以反映这些边。
template
MGraph::MGraph(T a[], int n, int e)
{
int i, j, k;
vertexNum = n;
edgeNum = e;
// 存储顶点
for (i = 0; i < vertexNum; i++) {
vertex[i] = a[i];
}
// 初始化邻接矩阵
for (i = 0; i < vertexNum; i++) {
for (j = 0; j < vertexNum; j++) {
edge[i][j] = 0;
}
}
// 依次输入每一条边
for (k = 0; k < edgeNum; k++) {
// 输入边依附的两个顶点的编号
cin >> i >> j;
// 置有边标志
edge[i][j] = 1;
edge[j][i] = 1;
}
}
3.析构函数---图的销毁。
template
MGraph::~MGraph(){}
4.添加顶点。
工作原理:
- 首先,函数检查当前的顶点数量(
vertexNum
)是否已经达到最大值(MaxSize
)。这是为了确保图没有达到其容量限制。- 如果图已满(即
vertexNum >= MaxSize
),则函数输出一个错误消息“图已满!”,并返回false
,表示添加顶点的操作失败。- 如果图未满,则将新顶点的值存储在
vertex
数组的下一个位置(即vertex[vertexNum]
),并将vertexNum
增加1,以表示图中的顶点数量增加了。- 最后,函数返回
true
,表示添加顶点的操作成功。
template
bool MGraph::addVertex(const T& newVertex) {
if (vertexNum >= MaxSize) {
cout << "图已满!" << endl;
return false;
}
vertex[vertexNum++] = newVertex;
return true;
}
5.删除顶点。
工作原理:
- 首先,函数检查输入的顶点索引是否有效。如果索引小于0或大于等于顶点数量(
vertexNum
),则函数输出一个错误消息“输入无效!”,并返回false
,表示删除顶点的操作失败。- 如果索引有效,则进入下一步操作。
- 接下来,函数遍历邻接矩阵的每一行和每一列,并将与要删除顶点相关的元素值设置为0。这样做是为了标记该顶点与其相邻顶点之间的边已被删除。
- 最后,函数返回
true
,表示删除顶点的操作成功完成
template
bool MGraph::removeVertex(int vertexIndex) {
if (vertexIndex < 0 || vertexIndex >= vertexNum) {
cout << "输入无效!" << endl;
return false;
}
for (int i = 0; i < vertexNum; i++) {
edge[i][vertexIndex] = edge[vertexIndex][i] = 0;
}
return true;
}
6.添加边。
工作原理:
- 首先,函数检查输入的索引是否有效。如果索引小于0或大于等于顶点数量(
vertexNum
),则函数输出一个错误消息“输入无效!”,并返回false
,表示添加边的操作失败。- 如果索引有效,则进入下一步操作。
- 接下来,函数将邻接矩阵中对应位置的值设置为1,表示在顶点i和顶点j之间存在一条边。由于这是一个无向图,因此同时设置
edge[i][j]
和edge[j][i]
为1。- 最后,函数返回
true
,表示添加边的操作成功完成
template
bool MGraph::addEdge(int i, int j) {
if (i < 0 || i >= vertexNum || j < 0 || j >= vertexNum) {
cout << "输入无效!" << endl;
return false;
}
edge[i][j] = edge[j][i] = 1;
return true;
}
7.删除边。
工作原理:
- 首先,函数检查输入的索引是否有效。如果索引小于0或大于等于顶点数量(
vertexNum
),或者在邻接矩阵中对应的值为0(表示没有边),则函数输出一个错误消息“输入无效!”,并返回false
,表示删除边的操作失败。- 如果索引有效,则进入下一步操作。
- 接下来,函数将邻接矩阵中对应位置的值设置为0,表示在顶点i和顶点j之间不存在边了。由于这是一个无向图,因此同时设置
edge[i][j]
和edge[j][i]
为0。- 最后,函数返回
true
,表示删除边的操作成功完成
template
bool MGraph::removeEdge(int i, int j) {
if (i < 0 || i >= vertexNum || j < 0 || j >= vertexNum || edge[i][j] == 0) {
cout << "输入无效!" << endl;
return false;
}
edge[i][j] = edge[j][i] = 0;
return true;
}
8.深度优先遍历。
工作原理:
- 首先,函数输出当前顶点的值(通过
cout << vertex[v];
)。- 然后,将当前顶点标记为已访问(通过
visited[v] = 1;
)。- 接下来,函数遍历所有与当前顶点直接相连的未访问顶点(通过
for
循环)。对于每一个未访问的相邻顶点j,如果该相邻顶点与当前顶点之间存在边(即edge[v][j] == 1
),则递归调用DFTraveres(j)
,继续进行深度优先遍历
template
void MGraph::DFTraveres(int v)
{
cout << vertex[v];
visited[v] = 1;
for (int j = 0; j < vertexNum; j++) {
if (edge[v][j] == 1 && visited[j] == 0) {
DFTraveres(j);
}
}
}
9.广度优先遍历。
工作原理:
- 首先,函数输出当前顶点的值(通过
cout << vertex[v];
)。- 然后,将当前顶点标记为已访问(通过
visited[v] = 1;
)。- 接下来,将已访问的顶点加入到队列中(通过
Q[++rear] = v;
)。队列使用数组Q
实现,其中front
和rear
分别表示队头和队尾的索引。- 进入循环,当队列非空时(通过
while (front!=rear)
),执行以下操作:将队头元素出队并送到变量w
中(通过w = Q[++front];
)。
- 遍历与当前顶点
w
直接相连的所有未访问顶点(通过for
循环)。对于每一个未访问的相邻顶点j,如果该相邻顶点与当前顶点之间存在边(即edge[w][j] == 1
),并且该相邻顶点尚未被访问(即visited[j] == 0
),则将该相邻顶点的值输出(通过cout << vertex[j];
),将其标记为已访问(通过visited[j] = 1;
),并将其加入到队列中(通过Q[++rear] = j;
)。
template
void MGraph::BFTraveres(int v)
{
// 采用顺序队列
int w, j, Q[MaxSize];
// 初始化队列
int front = -1, rear = -1;
cout << vertex[v];
visited[v] = 1;
// 被访问顶点入队
Q[++rear] = v;
// 当队列非空时
while (front!=rear){
// 将队头元素出队并送到v中
w = Q[++front];
for (j = 0; j < vertexNum; j++) {
if (edge[w][j] == 1 && visited[j] == 0) {
cout << vertex[j];
visited[j] = 1;
Q[++rear] = j;
}
}
}
}
10.判断图的连通性,不连通则计算连通分量的个数。
工作原理:
- 函数首先初始化一个变量
connectedComponents
为0,用于记录连通分量的数量。- 然后,函数遍历所有的顶点(通过
for
循环)。对于每一个未访问的顶点i(即visited[i] == 0
),如果该顶点尚未被访问,则递归调用DFTraveres(i)
进行深度优先遍历。在遍历过程中,所有被访问的顶点都会被标记为已访问(通过visited[i] = 1;
)。- 每次进行深度优先遍历后,连通分量的数量增加1(通过
connectedComponents++
)。- 遍历完成后,根据连通分量的数量判断图是否是连通的。如果连通分量的数量为1,则图是连通的,输出相应的消息;否则,图是非连通的,输出连通分量的数量。
- 最后,函数返回连通分量的数
template
int MGraph::isConnected() {
int connectedComponents = 0;
for (int i = 0; i < vertexNum; i++) {
if (visited[i] == 0) {
DFTraveres(i);
connectedComponents++;
}
}
// 如果连通分量数为1,则图是连通的,否则图是非连通的
if (connectedComponents == 1) {
cout << "图是连通的。" << endl;
}
else {
cout << "图是非连通的,连通分量数为: " << connectedComponents << endl;
}
return connectedComponents;
}
11.计算顶点的度。
工作原理:
- 函数首先声明一个整型数组
degree
,用于存储每个顶点的度。数组的大小为MaxSize
,这是预先定义的常数,表示图中顶点的最大数量。- 然后,函数初始化度数组为0,确保每个顶点的度初始化为0。
- 接下来,函数遍历所有的顶点(通过两个嵌套的
for
循环)。对于每对顶点i和j,如果边存在(即edge[i][j] == 1
),则将顶点i的度增加1(通过degree[i]++
)。- 最后,函数遍历所有的顶点,并输出每个顶点的度(通过
cout
语句)
template
void MGraph::calculateDegree() {
// 存储每个顶点的度
int degree[MaxSize];
// 初始化度数组为0
for (int i = 0; i < MaxSize; i++) {
degree[i] = 0;
}
// 计算每个顶点的度
for (int i = 0; i < vertexNum; i++) {
for (int j = 0; j < vertexNum; j++) {
if (edge[i][j] == 1) {
degree[i]++;
}
}
}
// 输出每个顶点的度
for (int i = 0; i < vertexNum; i++) {
cout << "顶点 " << vertex[i] << " : " << degree[i] << endl;
}
}
12.Prim算法,用于求解最小生成树问题。
工作原理:
- 函数首先声明了一些变量
i
,j
,k
,以及两个整型数组adjvex
和lowcost
,分别用于存储邻接点和最小权重。- 然后,函数初始化辅助数组。对于每个顶点i,
lowcost[i]
被初始化为从顶点v到顶点i的边的权重(通过edge[v][i]
),而adjvex[i]
被初始化为顶点v。- 接下来,函数将顶点v加入集合U,并将其最小权重设置为0。
- 然后,函数进入一个循环,迭代n-1次(通过
for (k = 1; k < vertexNum; k++)
)。在每次迭代中,函数执行以下操作:- 寻找具有最小权重的边对应的邻接点j(通过调用
MinEdge(lowcost, vertexNum)
函数)。
- 输出起点和终点以及权重(通过
cout
语句)。- 将顶点j加入集合U,并将其最小权重设置为0。
- 调整辅助数组。对于每个顶点i,如果存在一条从i到j的边且权重小于当前的最小权重(通过
edge[i][j] < lowcost[i]
),则更新最小权重和邻接点(通过lowcost[i] = edge[i][j]
和adjvex[i] =j
)。
template
void MGraph::Prim(int v)
{
int i, j, k;
int adjvex[MaxSize], lowcost[MaxSize];
// 初始化辅助数组
for (i = 0; i < vertexNum; i++) {
lowcost[i] = edge[v][i];
adjvex[i] = v;
}
// 将顶点v加入集合U
lowcost[v] = 0;
// 迭代n-1次
for (k = 1; k < vertexNum; k++) {
// 寻找最短边的邻接点 j
j = MinEdge(lowcost, vertexNum);
// 起点-终点 权重
cout << adjvex[j] << "-" << j << " " << lowcost[j] << endl;
// 顶点 j 加入集合U
lowcost[j] = 0;
// 调整辅助数组
for (i = 0; i < vertexNum; i++) {
if (edge[i][j] < lowcost[i]) {
lowcost[i] = edge[i][j];
adjvex[i] = j;
}
}
}
}
13.寻找最短边的邻接点。
工作原理:
- 函数首先声明了几个变量
min
,temp
, 和k
,分别用于存储当前最小权重、最小权重对应的顶点索引和循环计数器。- 然后,函数初始化
min
为一个大值(例如100000000),表示初始时没有找到更小的权重。- 接下来,函数遍历整个最小权重数组(通过
for
循环)。对于每个顶点k,如果当前最小权重小于min
且不为0(通过lowcost[k] < min && lowcost[k] != 0
),则更新min
为当前最小权重,并记录下对应的顶点索引temp
。- 最后,函数返回最小权重对应的顶点索引
temp
。
template
int MGraph::MinEdge(int lowcost[MaxSize], int vertexNum)
{
int min = 100000000;
int temp = 0, k;
for (k = 0; k < vertexNum; k++)
{
if (lowcost[k] < min && lowcost[k] != 0)
{
min = lowcost[k];
temp = k;
}
}
return temp;
}
14.主函数。
int main()
{
int i;
char ch[] = { 'A','B','C','D'};
cout << "【依次输入每一条边的两个顶点的编号】:\n";
MGraph MG{ch, 4, 4}; // 建立具有4个顶点 4条边的无向图
for (i = 0; i < MaxSize; i++) {
visited[i] = 0;
}
cout << "\n【深度优先遍历序列】:";
MG.DFTraveres(0); // 从顶点0出发进行深度优先遍历
for (i = 0; i < MaxSize; i++) {
visited[i] = 0;
}
cout << "\n【广度优先遍历序列】:";
MG.BFTraveres(0); // 从顶点0出发进行广度优先遍历
cout << endl;
for (i = 0; i < MaxSize; i++) {
visited[i] = 0;
}
cout << "\n【判断图的连通性】:";
MG.isConnected();
for (i = 0; i < MaxSize; i++) {
visited[i] = 0;
}
cout << "\n【各顶点的度】:\n";
MG.calculateDegree();
for (i = 0; i < MaxSize; i++) {
visited[i] = 0;
}
cout << "\n【Prim算法的最小生成树】:(起点-终点 权重)\n";
MG.Prim(0); // 从顶点0出发进行Prim算法
return 0;
}
完整代码链接:https://download.csdn.net/download/weixin_73286497/88758704
希望大家可以在该篇实验报告中有所收获,同时也感谢各位大佬的支持。文章如有任何问题请在评论区留言斧正,鸿蒙会尽快回复您的建议!