实验三 图的操作与实现
一、实验目的:
1、领会图的两种主要存储结构和图的基本运算算法设计;
2、领会图的两种遍历算法;
3、领会Prim算法求带权连通图中最小生成树的过程和相关算法设计;
4、掌握深度优先遍历和广度优先遍历算法在图路径搜索问题中的应用;
5、深入掌握图遍历算法在求解实际问题中的应用。
二、实验类型: 验证性/设计性/综合性
三、实验学时:4学时
四、实验教学的重点和难点:
重点:图的结构、基本操作及遍历算法
难点:图的主要应用
五、实验内容:
1、教材P310实验题1:实现图的邻接矩阵和邻接表的存储
编写一个程序graph.cpp,设计带权图的邻接矩阵与邻接表的创建和输出运算,并在此基础上设计一个主程序exp8-1.cpp完成以下功能。
(1)建立如图8.54所示的有向图G的邻接矩阵,并输出之。
(2)建立如图8.54所示的有向图G的邻接表,并输出之。
(3)销毁图G的邻接表。
图8.54 一个带权有向图
#include<iostream>
using namespace std;
#define MAXV 100 //最大顶点个数
struct GraphNode { // 图结点
int no; // 编号
char info; // 其他信息
};
struct MatrixGraph { // 邻接矩阵
int vexs; // 顶点数
int arcs; // 边数
int** p; // 动态创建二维邻接矩阵数组
GraphNode* vex; // 动态创建图结点数组
};
struct ANode { // 边结点
int adjvex; // 该边的邻接点编号
ANode* nextarc; // 指向下一个边结点的指针
int weight; // 边权值
ANode(int x, int y) :adjvex(x), weight(y) {} // 带初始化
};
struct HNode { // 头结点
ANode* firstarc; // 指向第一个边结点
HNode():firstarc(nullptr){}
};
struct AdjGraph { // 邻接表
int vexs; // 顶点数
int arcs; // 边数
HNode* head; // 邻接表的头结点数组
};
class solution {
public:
void CreateMG(MatrixGraph& mg, int A[MAXV][MAXV], int vexs, int arcs) { // 创建图的邻接矩阵
mg.arcs = arcs;
mg.vexs = vexs;
mg.p = new int* [mg.vexs];
for (int i = 0; i < mg.vexs; i++)mg.p[i] = new int[mg.vexs]; // 动态初始化n阶矩阵
for (int i = 0; i < vexs; i++)for (int j = 0; j < vexs; j++) { // 创建矩阵
if (A[i][j] != 0)mg.p[i][j] = A[i][j];
else mg.p[i][j] = 0xffff;
}
}
void PrintfMG(MatrixGraph& mg) { // 输出邻接矩阵
for (int i = 0; i < mg.vexs; i++) {
for (int j = 0; j < mg.vexs; j++) {
if (mg.p[i][j] == 0xffff)cout << "∞ ";
else cout << mg.p[i][j] << " ";
}
cout << endl;
}
}
void CreateAG(AdjGraph*& ag, int A[MAXV][MAXV], int vexs, int arcs) { // 创建图的邻接表
ANode* p;
ag->vexs = vexs;
ag->arcs = arcs;
ag->head = new HNode[vexs]; // 所有头结点指针域置为空
for (int i = 0; i < vexs; i++)for (int j = vexs - 1; j > -1; j--)if (A[i][j] != 0) {
p = new ANode(j, A[i][j]); // 放入边结点和权值
p->nextarc = ag->head[i].firstarc; // 头插法所以顺序倒过来从vexs-1开始到0
ag->head[i].firstarc = p;
}
}
void PrintfAG(AdjGraph*& ag) { // 输出邻接表
ANode* p; // 工作指针
for (int i = 0; i < ag->vexs; i++) {
p = ag->head[i].firstarc;
cout << i << ":";
while (p != nullptr) {
cout << p->adjvex << "[" << p->weight << "]→"; // 中括号内为边的权值
p = p->nextarc;
}
cout << "^" << endl;
}
}
void DeleteAG(AdjGraph* ag) { // 释放邻接表
ANode* pre, * p;
for (int i = 0; i < ag->vexs; i++) {
pre = ag->head[i].firstarc; // pre指向第i个单链表的头结点
if (pre != nullptr) { // 释放第i个单链表的所有边结点
p = pre->nextarc;
while (p != nullptr) {
delete pre;
pre = p;
p = p->nextarc;
}
delete pre;
}
}
delete ag->head;
delete ag;
}
};
int main()
{
MatrixGraph mg; // 定义邻接矩阵的变量
AdjGraph* ag = new AdjGraph(); // 定义邻接表的变量
int A[MAXV][MAXV] = { {0,5,0,7,0,0},// 以矩阵形式提前表达
{0,0,4,0,0,0},
{8,0,0,0,0,9},
{0,0,5,0,0,6},
{0,0,0,5,0,0},
{3,0,0,0,1,0}, };
int vexs = 6, arcs = 10;
solution s;
s.CreateMG(mg, A, vexs, arcs); // 创建图mj的邻接矩阵
cout << "图mg的邻接矩阵:" << endl;
s.PrintfMG(mg); // 输出图mj的邻接矩阵
s.CreateAG(ag, A, vexs, arcs); // 创建图aj的邻接表
cout << "图ag的邻接表:" << endl;
s.PrintfAG(ag); // 输出图ag的邻接表
s.DeleteAG(ag); // 销毁图ag的邻接表
}
2、教材P310实验题2:实现图的遍历算法
编写一个程序travsal.cpp实现图的两种遍历运算,并在此基础上设计一个程序exp8-2.cpp完成以下功能。
- 输出如图8.54的有向图G从顶点0开始的深度优先遍历序列(递归算法)。
- 输出如图8.54的有向图G从顶点0开始的深度优先遍历算法(非递归算法)。
- 输出如图8.54的有向图G从顶点0开始的广度优先遍历序列。
#include<iostream>
#include<stack>
#include<queue>
using namespace std;
#define MAXV 100 //最大顶点个数
struct ANode { // 边结点
int adjvex; // 该边的邻接点编号
ANode* nextarc; // 指向下一个边结点的指针
int weight; // 边权值
ANode(int x, int y) :adjvex(x), weight(y) {} // 带初始化
};
struct HNode { // 头结点
ANode* firstarc; // 指向第一个边结点
HNode():firstarc(nullptr){}
};
struct AdjGraph { // 邻接表
int vexs; // 顶点数
int arcs; // 边数
HNode* head; // 邻接表的头结点数组
};
class solution {
public:
void CreateAG(AdjGraph*& ag, int A[MAXV][MAXV], int vexs, int arcs) { // 创建图的邻接表
ANode* p;
ag->vexs = vexs;
ag->arcs = arcs;
ag->head = new HNode[vexs]; // 所有头结点指针域置为空
for (int i = 0; i < vexs; i++)for (int j = vexs - 1; j > -1; j--)if (A[i][j] != 0) {
p = new ANode(j, A[i][j]); // 放入边结点和权值
p->nextarc = ag->head[i].firstarc; // 头插法所以顺序倒过来从vexs-1开始到0
ag->head[i].firstarc = p;
}
}
int* visited0 = new int[32676] {0}; // 标记数组
void DFS(AdjGraph* ag, int no) { // 访问结点no 递归实现深度优先遍历
ANode* p = ag->head[no].firstarc; // 工作指针p指向结点no的第一个邻接点
cout << " " << no; // 输出被访问结点的编号
visited0[no] = 1; // 置已访问标记
while (p != nullptr) {
if (visited0[p->adjvex] == 0)DFS(ag, p->adjvex); // 若p->adjvex顶点未被访问,递归访问它
p = p->nextarc; // p指向结点no的下一个邻接点
}
}
void DFS2(AdjGraph* ag, int no) { // 访问结点no 非递归实现深度优先遍历
ANode* p; // 工作指针
stack<ANode*> StackHNode;
int* visited = new int[ag->vexs] {0}; // 标记数组
cout << "\n非递归实现深度优先遍历:" << no; // 输出被访问结点的编号
visited[no] = 1; // 置已访问标记
StackHNode.push(ag->head[no].firstarc); // 入栈
while (!StackHNode.empty()) { // 头结点未遍历完
p = StackHNode.top(); // 指向头结点
StackHNode.pop(); // 出栈
while (p) { // 直到走完这一头
if (visited[p->adjvex] == 0) { // 未访问
cout << " " << p->adjvex; // 输出被访问结点的编号
visited[p->adjvex] = 1; // 置已访问标记
StackHNode.push(ag->head[p->adjvex].firstarc); // 入栈
break; // break进入另一深度
}
p = p->nextarc; // 该结点访问过了访问它的邻接点
}
}
delete visited;
}
void BFS(AdjGraph* ag, int no) { // 访问结点no 广度优先遍历
ANode* p; // 工作指针
queue<int> qu;
int* visited = new int[ag->vexs] {0}; // 标记数组
cout << "\n广度优先遍历:" << no; // 输出被访问结点的编号
visited[no] = 1;
qu.push(no);
while (!qu.empty()) {
p = ag->head[qu.front()].firstarc; // 指向头结点的第一个邻接点
qu.pop(); // 出队
while (p != nullptr) { // 查找头结点的所有邻接点
if (visited[p->adjvex] == 0) { // 当前邻接点未被访问
cout << " " << p->adjvex; // 输出被访问结点的编号
visited[p->adjvex] = 1; // 置已访问标记
qu.push(p->adjvex); // 该结点入队
}
p = p->nextarc; // 找下一个邻接点
}
}
}
};
int main()
{
AdjGraph* ag = new AdjGraph; // 定义邻接表的变量
int A[MAXV][MAXV] = { {0,5,0,7,0,0},// 以矩阵形式提前表达
{0,0,4,0,0,0},
{8,0,0,0,0,9},
{0,0,5,0,0,6},
{0,0,0,5,0,0},
{3,0,0,0,1,0}, };
int vexs = 6, arcs = 10;
solution s;
s.CreateAG(ag, A, vexs, arcs); // 创建图aj的邻接表
cout << "递归实现深度优先遍历:";
s.DFS(ag, 0); // 递归实现深度优先遍历
s.DFS2(ag, 0); // 非递归实现深度优先遍历
s.BFS(ag, 0); // 广度优先遍历
}
3、教材P311实验题5:采用Prim算法求最小生成树
编写一个程序exp8-5.cpp,实现求带权连通图中最小生成树的Prim算法,如图8.55所示的带权连通图G,输出从顶点0出发的一棵最小生成树。
图8.55 一个带权连通图
#include<iostream>
#include<stack>
#include<queue>
using namespace std;
#define MAXV 100 //最大顶点个数
struct MatrixGraph { // 邻接矩阵
int vexs; // 顶点数
int arcs; // 边数
int** p; // 动态创建二维邻接矩阵数组
};
class solution {
public:
void CreateMG(MatrixGraph& mg, int A[MAXV][MAXV], int vexs, int arcs) { // 创建图的邻接矩阵
mg.arcs = arcs;
mg.vexs = vexs;
mg.p = new int* [mg.vexs];
for (int i = 0; i < mg.vexs; i++)mg.p[i] = new int[mg.vexs]; // 动态初始化n阶矩阵
for (int i = 0; i < vexs; i++)for (int j = 0; j < vexs; j++) { // 创建矩阵
if (A[i][j] != 0)mg.p[i][j] = A[i][j];
else mg.p[i][j] = 0xffff; // 为0的时候无穷远
}
}
void PrintfPrim(MatrixGraph g, int no) { // 输出从结点no出发的一棵最小生成树(从U出发在V-U中招距离最近的点)
cout << no << "出发的最小生成树如下\n";
int U_min[MAXV], begin_end[MAXV], back, mindist; // U_min表示U结点们所连最小边,begin_min标记最小边的起始点,mindist最小边权,front为前一个结点,back为后一个结点
for (int i = 0; i < g.vexs; i++) { // 给数组U_min[],begin_end[]设置初值
U_min[i] = g.p[no][i]; // 一开始U_min只有no所连的边
begin_end[i] = no; // 一开始起始点都是no
}
U_min[no]=0; // 标记该点no已加入U
for (int i = 1; i < g.vexs; i++) { // 找出另外(n-1)个顶点
mindist = 0xffff;
for (int j = 0; j < g.vexs; j++) { // 在(V-U)中找到离U最近的结点back
if (U_min[j] != 0 && U_min[j] < mindist) { // U_min=-1表示该点以被用,U_min=0表示该边不存在
mindist = U_min[j];
back = j; // back记录 最 近 结点的编号
}
}
cout << "边(" << begin_end[back] << "," << back << ")权值:" << mindist << endl; //输出最小生成树的一条边
U_min[back] = 0; // 标记该点back已加入U
for (int j = 0; j < g.vexs; j++)if (U_min[j] != 0 && g.p[back][j] < U_min[j]) { // 更新(V-U)
U_min[j] = g.p[back][j];
begin_end[j] = back;
}
}
}
};
int main()
{
MatrixGraph mg; // 定义邻接矩阵的变量
int A[MAXV][MAXV] = { {0,5,8,7,0,3},// 以矩阵形式提前表达
{5,0,4,0,0,0},
{8,4,0,5,0,9},
{7,0,5,0,5,6},
{0,0,0,5,0,1},
{3,0,9,6,1,0}, };
int vexs = 6, arcs = 10;
solution s;
s.CreateMG(mg, A, vexs, arcs); // 创建图mj的邻接矩阵
s.PrintfPrim(mg, 0); //输出mg最小生成树
}
4、教材P311实验题10:求有向图的简单路径
编写一个程序exp8-10.cpp,设计相关算法完成以下功能。
- 输出如图8.56的有向图G从顶点5到顶点2的所有简单路径。
- 输出如图8.56的有向图G从顶点5到顶点2的所有长度为3的简单路径。
- 输出如图8.56的有向图G从顶点5到顶点2的最短路径。
#include<iostream>
#include<queue>
using namespace std;
#define MAXV 100 //最大顶点个数
struct MatrixGraph { // 邻接矩阵
int vexs; // 顶点数
int arcs; // 边数
int** p; // 动态创建二维邻接矩阵数组
};
class solution {
public:
int path[MAXV];
int visited[MAXV]{ 0 };
void CreateMG(MatrixGraph& mg, int A[MAXV][MAXV], int vexs, int arcs = 0) { // 创建图的邻接矩阵,懒得数边数且边数没用的时候直接不输入了
mg.arcs = arcs;
mg.vexs = vexs;
mg.p = new int* [mg.vexs];
for (int i = 0; i < mg.vexs; i++)mg.p[i] = new int[mg.vexs]; // 动态初始化n阶矩阵
for (int i = 0; i < vexs; i++)for (int j = 0; j < vexs; j++) { // 创建矩阵
if (A[i][j] != 0)mg.p[i][j] = A[i][j];
else mg.p[i][j] = 0xffff; // 为0的时候无穷远
}
}
void FindSimple(MatrixGraph mg, int front, int end, int d = 0) { // 找两点间的简单路径
visited[front] = 1; // 标记该点已经过
path[d] = front; // 存入路径中
if (front == end) { // 到达目标,输出
for (int i = 0; i <= d; i++)cout << path[i] << " \n"[i == d];
return;
}
for (int i = 0; i < mg.vexs; i++) { // 遍历front该点每一条可能存在的边
if (mg.p[front][i] < 0xffff && !visited[i]) { // 寻找存在且未用过的边
FindSimple(mg, i, end, d + 1); // 递归
visited[i] = 0; // 回溯
}
}
}
void FindSimpleLength(MatrixGraph mg, int front, int end, int length, int d = 0) {
visited[front] = 1; // 标记该点已经过
path[d] = front; // 存入路径中
if (front == end && length == d + 1) { // 到达目标且长度适合,输出
for (int i = 0; i <= d; i++)cout << path[i] << " \n"[i == d];
return;
}
for (int i = 0; i < mg.vexs; i++) { // 遍历front该点每一条可能存在的边
if (mg.p[front][i] < 0xffff && !visited[i]) { // 寻找存在且未用过的边
FindSimpleLength(mg, i, end, length, d + 1); // 递归
visited[i] = 0; // 回溯
}
}
}
void BFS(MatrixGraph mg, int front, int end) { // 广度优先搜索寻找最短路径,将寻找过的路径标记(重复指向这些路径则不是最优解)
int last[MAXV]; // 追踪数组,追踪前一个结点
queue<int>p;
p.push(front);
visited[front] = 1; // 标记
last[front] = -1; // 便于结束
int now;
while (!p.empty()) { // 非空说明没遍历完
now = p.front();
p.pop(); // 出栈去遍历
if (now == end)break; // 找到结束点不用再遍历
for (int i = 0; i < mg.vexs; i++) { // 遍历
if (!visited[i] && mg.p[now][i] < 0xffff) { // 寻找路径存在并且未访问的点
visited[i] = 1; // 标记
last[i] = now; // 追踪前一个结点
p.push(i); // 存入队列
}
}
}
int i = 0; // 路径数组下标
while (now != -1) {
path[i++] = now; // 追踪的结点放入路径中
now = last[now]; // 不断追踪前结点直至起点(起点last=-1结束循环)
}
while (i) {
cout << path[--i] << " \n"[i == 1]; // 输出结果
}
}
};
int main() {
int A[MAXV][MAXV] = { {0,1,0,1,0,0},// 以矩阵形式提前表达
{0,0,1,0,0,0},
{1,0,0,0,0,1},
{0,0,1,0,0,1},
{0,0,0,1,0,0},
{1,1,0,1,1,0}, };
MatrixGraph mg;
solution s;
s.CreateMG(mg, A, 6); // 创建图mg的邻接矩阵
cout << "\n该图5到2的所有路径如下\n";
s.FindSimple(mg, 5, 2); // 找到mg中5->2的所有路径
cout << "\n该图5到2的长度为3的路径如下\n";
s.FindSimpleLength(mg, 5, 2, 3); // 找到mg中5->2的长度为3的所有路径
cout << "\n该图5到2的最短路径\n";
s.BFS(mg, 5, 2); // 找到mg中5->2的最短路径
}
5、教材P313实验题14:用图搜索方法求解如图3.28(教材P119)的迷宫问题(也可以自建迷宫)
编写一个程序exp8-14.cpp,完成以下功能。
- 建立一个迷宫对应的邻接表表示。
- 采用深度优先遍历算法输出从入口(1,1)到出口(M,N)的所有迷宫路径。
图3.28 迷宫示意图
#include<iostream>
using namespace std;
#define MAXV 100 //最大顶点个数
struct ANode { // 边结点
int x; // 该边的邻接点编号
int y; // 该边的邻接点编号
ANode* nextarc; // 指向下一个边结点的指 针
ANode(int m, int n) :x(m), y(n), nextarc(nullptr) {}
};
struct HNode { // 头结点
ANode* firstarc; // 指向第一个边结点
HNode():firstarc(nullptr){}
};
struct AdjGraph { // 邻接表
int vexs; // 顶点数
int arcs; // 边数
HNode** head; // 邻接表的头结点数组
};
class solution {
public:
ANode* path2[MAXV];
int visited2[MAXV][MAXV]{ 0 };
int kk[4][2] = { {-1,0},{1,0},{0,-1},{0,1} }; // 临边数组
void CreateAG(AdjGraph*& ag, int A[MAXV][MAXV], int vexs, int arcs = 0) { // 创建图的邻接表
ANode* p;
ag->vexs = vexs;
ag->arcs = arcs;
ag->head = new HNode*[vexs];
for (int i = 0; i < ag->vexs; i++)ag->head[i] = new HNode[vexs]; // 所有头结点指针域置为空
for (int i = 0; i < vexs; i++)for (int j = vexs - 1; j > -1; j--)if (A[i][j])
for (int k = 0; k < 4; k++) if (A[i + kk[k][0]][j + kk[k][1]]) {
p = new ANode(i + kk[k][0], j + kk[k][1]);
p->nextarc = ag->head[i][j].firstarc; // 头插法所以顺序倒过来从vexs-1开始到0
ag->head[i][j].firstarc = p;
}
}
void FindSimple(AdjGraph* ag, int x1, int y1, int x2, int y2, int d = 0) { // 深度优先搜索
visited2[x1][y1] = 1; // 标记该点 已经过
path2[d] = new ANode(x1, y1); // 存入路径
if (x1 == x2 && y1 == y2) { // 到达目标,输出
for (int i = 0; i <= d; i++)cout << "(" << path2[i]->x << "," << path2[i]->y << ")" << " \n"[i == d];
return;
}
ANode* p = ag->head[x1][y1].firstarc; // 指向头结点的第一个邻接点
while (p) { // 深度优先搜索完其临边
if (visited2[p->x][p->y] == 0) { // 未被访问
FindSimple(ag, p->x, p->y, x2, y2, d + 1); // 递归
visited2[p->x][p->y] = 0; // 回溯
}
p = p->nextarc; // 找下一个邻接点
}
}
};
int main() {
int A[MAXV][MAXV] = { {0,0,0,0,0,0},// 以矩阵形式提前表达
{0,1,1,1,0,0},
{0,1,0,1,1,0},
{0,1,1,1,0,0},
{0,0,1,1,1,0},
{0,0,0,0,0,0}, };
AdjGraph* ag = new AdjGraph;
solution s;
s.CreateAG(ag, A, 6); // 创建图aj的邻接表
cout << "该迷宫(1,1)到(4,4)的所有路径如下\n";
s.FindSimple(ag, 1, 1, 4, 4); // 找到迷宫(1,1)到(4,4)的所有路径
}