系列文章:
数据结构实验一 线性表、堆栈和队列的操作与实现
数据结构实验二 二叉树的操作与实现
数据结构实验三 图的操作与实现
数据结构实验四 查找和排序算法实现
一、实验目的:
1、领会图的两种主要存储结构和图的基本运算算法设计;
2、领会图的两种遍历算法;
3、领会Prim算法求带权连通图中最小生成树的过程和相关算法设计;
4、掌握深度优先遍历和广度优先遍历算法在图路径搜索问题中的应用;
5、深入掌握图遍历算法在求解实际问题中的应用。
二、使用仪器、器材
微机一台
操作系统:WinXP
编程软件:C/C++编程软件
三、实验内容及原理
填入自己的内容(思路或算法流程图、源代码、说明等)
1、教材P310实验题1:实现图的邻接矩阵和邻接表的存储
编写一个程序graph.cpp,设计带权图的邻接矩阵与邻接表的创建和输出运算,并在此基础上设计一个主程序exp8-1.cpp完成以下功能。
(1)建立如图8.54所示的有向图G的邻接矩阵,并输出之。
(2)建立如图8.54所示的有向图G的邻接表,并输出之。
(3)销毁图G的邻接表。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 1024 //最大顶点数
int matrix[maxn][maxn]; //邻接矩阵
int n,m; //顶点数,边数
/*
边结点
*/
struct ArcNode{
int adjvex; //该边所指向的顶点的位置
int lowcost; //权值
ArcNode* next; //指向的下一条边的指针
};
ArcNode* ArcList[maxn*(maxn-1)]; //所有边结点
int in = 0; //下标
/*
顶点
*/
struct{
ArcNode* firstarc;
}AdjList[maxn];
/*
增加一条从i指向j的权值为k的顶点
*/
void add(int i,int j,int k){
matrix[i][j] = k;
ArcNode* p = new ArcNode();
p->adjvex = j; //它指向的是j顶点
p->lowcost = k; //权值为k
//p插入到链表头部
p->next = AdjList[i].firstarc;
AdjList[i].firstarc = p;
ArcList[in++] = p; //把这个边结点存储到数组中
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
n = 6;
m = 10;
//初始化邻接矩阵
for(int i = 0;i<n;++i){
for(int j = 0;j<n;++j){
matrix[i][j] = -1;
}
}
//初始化AdjList
for(int i = 0;i<n;++i){
AdjList[i].firstarc = NULL;
}
//增加m条边
add(0,1,5);
add(0,3,3);
add(1,2,4);
add(2,0,8);
add(2,5,9);
add(3,2,5);
add(3,5,6);
add(4,3,5);
add(5,4,1);
add(5,0,3);
//打印邻接矩阵
cout << "adjacent matrix:" << endl;
for(int i = 0;i<n;++i){
for(int j = 0;j<n;++j){
cout << matrix[i][j] << " \n"[j == n-1];
}
}
//打印邻接表
cout << "adjacency list:" << endl;
ArcNode* p = 0;
for(int i = 0;i < n;++i){
p = AdjList[i].firstarc;
cout << i << " : ";
while(p){
cout << p->adjvex << "(" << p->lowcost << ")" << " -> ";
p = p->next;
}
cout << "^" << endl;
}
p = 0;
//销毁邻接表
for(int i = 0;i<in;++i){
delete ArcList[i]; //删除
ArcList[i] = 0; //指针置空
}
//修改每个顶点的firstarc为空
for(int i = 0;i<n;++i){
AdjList[i].firstarc = 0;
}
in = 0;
return 0;
}
2、教材P310实验题2:实现图的遍历算法
编写一个程序travsal.cpp实现图的两种遍历运算,并在此基础上设计一个程序exp8-2.cpp完成以下功能。
(1) 输出如图8.54的有向图G从顶点0开始的深度优先遍历序列(递归算法)。
(2) 输出如图8.54的有向图G从顶点0开始的深度优先遍历算法(非递归算法)。
(3) 输出如图8.54的有向图G从顶点0开始的广度优先遍历序列。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 1024 //最大顶点数
int n,m; //顶点数,边数
/*
边结点
*/
struct ArcNode{
int adjvex; //该边所指向的顶点的位置
int lowcost; //权值
ArcNode* next; //指向的下一条边的指针
};
ArcNode* ArcList[maxn*(maxn-1)]; //所有边结点
int in = 0; //下标
/*
顶点
*/
struct{
ArcNode* firstarc;
}AdjList[maxn];
/*
增加一条从i指向j的权值为k的顶点
*/
void add(int i,int j,int k){
ArcNode* p = new ArcNode();
p->adjvex = j; //它指向的是j顶点
p->lowcost = k; //权值为k
//p插入到链表头部
p->next = AdjList[i].firstarc;
AdjList[i].firstarc = p;
ArcList[in++] = p; //把这个边结点存储到数组中
}
/*
销毁邻接表
*/
void destoryArc(){
//销毁邻接表
for(int i = 0;i<in;++i){
delete ArcList[i]; //删除
ArcList[i] = 0; //指针置空
}
//修改每个顶点的firstarc为空
for(int i = 0;i<n;++i){
AdjList[i].firstarc = 0;
}
}
bool tag[maxn]; //访问标记
/*
深度优先遍历序列(递归)
v : 当前访问的顶点
*/
void dfs1(int v = 0){
cout << v << " "; //输出这个顶点
tag[v] = 1; //给这个顶点加入标记
ArcNode* p = AdjList[v].firstarc; //邻接表
while(p){ //遍历所有和这个顶点相连的边
if(!tag[p->adjvex]) //如果准备访问的顶点没加访问标记,就加入
dfs1(p->adjvex); //递归
p = p->next; //指针后移
}
p = 0; //指针置空
}
int stk[maxn]; //模拟栈
int si = 0; //栈里元素个数
/*
深度优先遍历 非递归
*/
void dfs2(){
memset(tag,0,sizeof(tag)); //清空访问标记
int v = 0; //当前访问的顶点
si = 0; //清空栈的元素
stk[si++] = v; //入栈
tag[v] = 1; //标记初始顶点
cout << "dfs(not recursion):";
ArcNode*p = 0;
while(si){
//只要栈内还有元素,就一直循环
v = stk[--si]; //出栈
cout << v << " "; //输出
p = AdjList[v].firstarc;
while(p){
if(!tag[p->adjvex]){
//只要没被访问过就入栈
stk[si++] = p->adjvex;
tag[p->adjvex] = 1; //标记
}
p = p->next; //指针后移
}
p = 0; //指针置空
}
cout << endl;
}
int que[maxn]; //队列
int qi = 0,qj = 0; //头指针和尾指针
/*
广度优先
*/
void bfs(){
memset(tag,0,sizeof(tag)); //清空访问标记
int v = 0; //当前顶点
qi = 0;
qj = 0;
tag[v] = 1; //标记初始顶点
cout << "bfs:";
que[qj++] = v; //入队
ArcNode*p = 0;
while(qi != qj){
//只要队列不空,就一直循环
v = que[qi++]; //出队
cout << v << " "; //输出顶点
qi %= maxn; //如果qi >= maxn,则从0开始
p = AdjList[v].firstarc;
while(p){
if(!tag[p->adjvex]){
//只要没被访问过就入栈
que[qj++] = p->adjvex;
qj %= maxn;
tag[p->adjvex] = 1; //标记
}
p = p->next; //指针后移
}
p = 0; //指针置空
}
cout << endl;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
n = 6;
m = 10;
//初始化AdjList
for(int i = 0;i<n;++i){
AdjList[i].firstarc = NULL;
}
//增加m条边
add(0,1,5);
add(0,3,3);
add(1,2,4);
add(2,0,8);
add(2,5,9);
add(3,2,5);
add(3,5,6);
add(4,3,5);
add(5,4,1);
add(5,0,3);
//-----------深度优先(递归)----------
memset(tag,0,sizeof(tag)); //清空访问标记
cout << "dfs(recursion):";
dfs1();
cout << endl;
memset(tag,0,sizeof(tag)); //清空访问标记
//-----------深度优先(非递归)----------
dfs2();
//-----------广度优先----------
bfs();
destoryArc(); //销毁邻接表
return 0;
}
3、教材P311实验题5:采用Prim算法求最小生成树
编写一个程序exp8-5.cpp,实现求带权连通图中最小生成树的Prim算法,如图8.55所示的带权连通图G,输出从顶点0出发的一棵最小生成树。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 100
int matrix[maxn][maxn]; //邻接矩阵
int n,m;
bool tag[maxn]; //标记,tag[i] = true表示顶点i在集合U中
struct{
int adjvex; //最小边在U中的那个顶点
int lowcost; //权值
}closedge[maxn];
/*
增加一条连接i和j的权值为k的边
*/
void add(int i,int j,int k){
matrix[i][j] = k;
matrix[j][i] = k;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//初始化邻接矩阵
for(int i = 0;i < maxn;++i){
for(int j = 0;j < maxn;++j){
matrix[i][j] = -1;
}
}
n = 6; //顶点6个
m = 10; //边10条
add(0,1,5);
add(1,2,4);
add(2,3,5);
add(3,4,5);
add(4,5,1);
add(5,0,3);
add(0,2,8);
add(0,3,7);
add(2,5,9);
add(3,5,6);
int v = 0; //当前顶点为0
tag[0] = 1; //从顶点0出发,所以要把顶点0加入到集合U
//初始化closedge
for(int i = 0;i<n;++i){
closedge[i] = {0,matrix[i][0]};
}
int T = n-1;
//循环执行n-1次
while(T--){
//寻找closedge里一端不在集合U中的边
int mi = INT_MAX;
for(int i = 0;i<n;++i){
if(!tag[i] && closedge[i].lowcost != -1 && closedge[i].lowcost < mi){
mi = closedge[i].lowcost;
v = i;
}
}
tag[v] = 1; //加入集合U中
cout << v << " --- " << closedge[v].adjvex << endl; //输出这条边
//更新lowcost
for(int i = 1;i<n;++i){
if(!tag[i] && (closedge[i].lowcost == -1 || (matrix[i][v] != -1 && closedge[i].lowcost > matrix[i][v]))){
closedge[i].lowcost = matrix[i][v];
closedge[i].adjvex = v;
}
}
}
return 0;
}
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的最短路径。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 100 //最大顶点数
int matrix[maxn][maxn];//邻接矩阵
int n,m; //顶点个数,边的个数
bool tag[maxn]; //标记这个顶点是否已经访问过
int tmp[maxn]; //记录路径
/*
深度优先遍历找所有路径
now:当前顶点
dest:目标
i:当前长度
*/
void dfs(int now,int dest,int i = 0){
tmp[i] = now;
if(now == dest){
//到达终点,输出
for(int j = 0;j<=i;++j){
cout << tmp[j] << " \n"[j == i];
}
return;
}
//遍历从该顶点出发的所有边
for(int j = 0;j<n;++j){
if(matrix[now][j] && !tag[j]){
//边存在并且未被访问过
tag[j] = 1; //标记
dfs(j,dest,i+1); //递归
tag[j] = 0; //取消标记
}
}
}
/*
深度优先遍历找指定长度的所有路径
now:当前顶点
dest:目标
len:指定长度
i:当前长度
*/
void dfs1(int now,int dest,int len,int i = 0){
tmp[i] = now;
if(now == dest){
//到达终点,并且长度为len,输出
if(i == len)
for(int j = 0;j<=i;++j){
cout << tmp[j] << " \n"[j == i];
}
return;
}
if(i >= len){
return;
}
//遍历从该顶点出发的所有边
for(int j = 0;j<n;++j){
if(matrix[now][j] && !tag[j]){
//边存在并且未被访问过
tag[j] = 1; //标记
dfs1(j,dest,len,i+1); //递归
tag[j] = 0; //取消标记
}
}
}
/*
使用广度优先遍历计算从start到end的最短路径
*/
int last[maxn]; //记录在bfs过程中的顶点的上一顶点
void bfs(int start,int end){
queue<int> q; //队列
memset(tag,0,sizeof(tag)); //重置标记
memset(last,-1,sizeof(last)); //重置上一顶点的记录
cout << "Shortest path from vertex 5 to vertex 2 is:" << endl;
q.push(start); //把开始顶点加入到队列
tag[start] = 1; //给开始顶点添加标记
int v; //当前顶点
while(!q.empty()){
//取出队列中的第一个元素
v = q.front();
q.pop();
if(v == end){
//找到结束点
break;
}
//遍历从v开始的边
for(int i = 0;i<n;++i){
//路径存在并且未被访问过
if(!tag[i] && matrix[v][i]){
tag[i] = 1; //标记
last[i] = v; //记录上一顶点
q.push(i);
}
}
}
int i = 0; //tmp数组的下标(路径长度)
v = end;
while(v != -1){
tmp[i++] = v;
v = last[v]; //跳到上一顶点
}
//输出路径
while(i){
cout << tmp[--i] << " ";
}
cout << endl;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
// 初始化邻接矩阵
for(int i = 0;i<n;++i){
for(int j = 0;j<n;++j){
matrix[i][j] = 0;
}
}
n = 6;
m = 12;
//下面m行建图
matrix[0][1] = 1;
matrix[0][3] = 1;
matrix[1][2] = 1;
matrix[2][5] = 1;
matrix[2][0] = 1;
matrix[3][2] = 1;
matrix[3][5] = 1;
matrix[4][3] = 1;
matrix[5][4] = 1;
matrix[5][3] = 1;
matrix[5][1] = 1;
matrix[5][0] = 1;
//(1) 输出有向图G从顶点5到顶点2的所有简单路径。
cout << "All the paths from vertex 5 to vertex 2:"<< endl;
memset(tag,0,sizeof(0)); //初始化tag
tag[5] = 1; //标记5
dfs(5,2);
tag[5] = 0; //取消标记5
//(2) 输出有向图G从顶点5到顶点2的所有长度为3的简单路径。
cout << "All paths from vertex 5 to vertex 2 of length 3:" << endl;
memset(tag,0,sizeof(0)); //初始化tag
tag[5] = 1; //标记5
dfs1(5,2,3);
tag[5] = 0; //取消标记5
// (3)输出有向图G从顶点5到顶点2的最短路径。
bfs(5,2);
return 0;
}
5、教材P313实验题14:用图搜索方法求解如图3.28(教材P119)的迷宫问题(也可以自建迷宫)
编写一个程序exp8-14.cpp,完成以下功能。
(1) 建立一个迷宫对应的邻接表表示。
(2) 采用深度优先遍历算法输出从入口(1,1)到出口(M,N)的所有迷宫路径。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct ArcNode; //提前声明一下
int m[6][6] = {
{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}
}; //地图
int DIRECTIONS[2][4] = {{1,-1,0,0},{0,0,1,-1}};
/*
顶点
*/
struct Point{
int x,y;
ArcNode* firstArc;
}*mp[6][6];
/*
边
*/
struct ArcNode{
Point* p; //该边指向的顶点
ArcNode* next; //下一个指针
};
bool tag[6][6]; //标记是否有被访问过
/*
深度优先搜索
x,y:坐标
i:path的下标
*/
Point* path[6*6]; //记录路径
void dfs(int x,int y,int i = 0){
path[i] = mp[x][y];
if(x == 4 && y == 4){
//到达终点
for(int j = 0;j<=i;++j){
cout << "(" << path[j]->x << "," << path[j]->y << ")" << " \n"[j == i]; //输出路径
}
return;
}
ArcNode*p = 0; //邻接链表
p = mp[x][y]->firstArc; //p一开始指向firstArc
int xi,yi;
while(p){ //只要p不为空,就一直循环
xi = p->p->x;
yi = p->p->y;
if(!tag[xi][yi]){
//只有没被访问过才能访问
tag[xi][yi] = 1; //标记
dfs(xi,yi,i+1); //递归
tag[xi][yi] = 0; //取消标记
}
p = p->next;
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//初始化mp数组
for(int i = 0;i<6;++i){
for(int j = 0;j<6;++j){
if(m[i][j]){
mp[i][j] = new Point();
mp[i][j]->x = i;
mp[i][j]->y = j;
mp[i][j]->firstArc = 0;
}else{
mp[i][j] = 0;
}
}
}
//初始化邻接表
int x,y;
for(int i = 1;i<=4;++i){
for(int j = 1;j<=4;++j){
if(m[i][j]){
for(int k = 0;k<4;++k){
x = i + DIRECTIONS[0][k];
y = j + DIRECTIONS[1][k];
if(m[x][y]){
//地图上有点
ArcNode* p = new ArcNode();
p->p = mp[x][y];
//链表常规插入操作
p->next = mp[i][j]->firstArc;
mp[i][j]->firstArc = p;
}
}
}
}
}
memset(tag,0,sizeof(tag)); //初始化tag数组
cout << "The maze path from entry to exit is as follows:" << endl;
tag[1][1] = 1; //给起点添加访问标记
dfs(1,1); //深度优先
return 0;
}