数据结构实验三 图的操作与实现

实验三  的操作与实现

一、实验目的:

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完成以下功能。

  1. 输出如图8.54的有向图G从顶点0开始的深度优先遍历序列(递归算法)。
  2. 输出如图8.54的有向图G从顶点0开始的深度优先遍历算法(非递归算法)。
  3. 输出如图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,设计相关算法完成以下功能。

  1. 输出如图8.56的有向图G从顶点5到顶点2的所有简单路径。
  2. 输出如图8.56的有向图G从顶点5到顶点2的所有长度为3的简单路径。
  3. 输出如图8.56的有向图G从顶点5到顶点2的最短路径。

图8.56 一个有向图

#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. 建立一个迷宫对应的邻接表表示。
  2. 采用深度优先遍历算法输出从入口(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)的所有路径
}

  • 30
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌白萌白的傻白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值