目录
近来有空闲,把前几个学期做的实验上传上来。如有错误的地方欢迎大佬批评指正,有更好的方法也期待您的分享~
实验内容
1.编写函数,采用邻接矩阵表示法,构造一个无向网。
2.编写函数,实现从键盘输入数据,建立一个有向图的邻接表。
3.编写函数,输出该邻接表。
4.编写函数,采用邻接表存储实现无向图的深度优先非递归遍历。
5.编写函数,采用邻接表存储实现无向图的广度优先遍历。
6.编写一个主函数,在主函数中设计一个简单的菜单,分别调试上述算法。
一、实验目的
1.熟悉理解图的基本概念、逻辑结构以及存储结构;
2.掌握编程实现图遍历具体算法:深度优先、广度优先;
3.深刻理解图的顺序存储(邻接矩阵)与链式存储(邻接链表)的特性;
4.训练在编程上控制复杂结构的能力,为今后控制更为复杂结构,进而解决有一定难度的复杂问题奠定基础。
二、问题分析及数据结构设计
本次开发任务要求我们编写一个能调试几种构造和搜索图的系统。该任务需要实现以下功能:构造无向网、建立与输出邻接表、深度优先非递归遍历无向图、广度优先遍历无向图等。
其次是数据结构设计,为了实现这些功能,我们需要构建数据模型。图的存储结构除了要存储图中各个顶点本身的信息外,同时还要存储顶点与顶点之间的所有关系(边的信息)。常用的图的存储结构主要有邻接矩阵和邻接表。
首先定义顶点最大个数 MAX_VERTEX_NUM 为100。
定义邻接矩阵中顶点数目为vexnum,边的数目为arcnum;定义一维数组vexs[MAX_VERTEX_NUM]存储顶点信息;定义二维数组arcs[MAX_VERTEX_NUM] [MAX_VERTEX_NUM]存储顶点与顶点之间的关系。
定义邻接表中顶点的数据类型为VerTexData;定义边结点结构体node中,整型目标节点dest,整型边的权值cost,下一边链接指针link;定义顶点结点结构体 VerTexNode 中,顶点数据域data,边链表头指针adj;定义邻接表结构体为 VexList[MAX_VERTEX_NUM],整型顶点和边的实际数分别为n和e。
对于每个功能,我们需要设计相应的函数,函数的输入参数和返回值根据具体的需求进行设计。例如,建立无向图的邻接矩阵的函数使用字符序列g作为输入,没有返回值;非递归深度遍历函数使用节点指针G作为输入,输出遍历的结果等。另外,在实现广度优先遍历功能的过程中,要实现重放也需要额外的存储空间,利用队列的先入先出特性,定义队列q存储顶点信息。
三、算法设计
1.采用邻接矩阵创建无向网 CreateGraph():
(1)用户输入两数字,确定该网的顶点个数G.vexnum与边数G.edgenum;
(2)for循环对顶点G.vertex[i]进行赋值;
(3)for循环输入想要添加的边的权值以及边所依附的两顶点;
(4)调用LocateVex()函数确定两顶点在图中的位置;
(5)对无向图各顶点对称赋值;
(6)for循环输出顶点及权值。
![](https://img-blog.csdnimg.cn/direct/5fe043727e6945d4a914cfd6f7bd37be.png)
2.创建有向图的邻接表 CreatGraph():
(1)用户输入两数字,确定该网的顶点个数G.n与边数G.e;
(2)for循环输入顶点信息G.VexList[i].data,将G.VexList[i].adj置空,表示该点已经访问过;
(3)逐条边输入,分别输入尾tail,头head,权重weight,创建边链表头指针 p,p->dest指向头head,p->cost指向权重weight。
(4)调用ShowGraph()函数,for循环输出邻接表的结点信息,及其指向的结点、边权值。
![](https://img-blog.csdnimg.cn/direct/22bce50daf2d4557b0da9b6b9f715ebe.png)
3.邻接表深度优先非递归遍历 dfs():
(1)初始化栈
(2)输出起始的顶点,起始顶点改为“已访问”的标志,将起始顶点进栈
(3)重复一下的操作直至栈为空:
(4)取栈顶元素顶点,存在着未被访问的邻结点W
(5)输出顶点W,将顶点W改为“已访问”,将W进栈;
(6)否则,当前顶点退栈;
![](https://img-blog.csdnimg.cn/direct/0b9a66f93e294955bccebf12e57cdd66.png)
4.无向图广度优先递归遍历 bfs():
(1)图存在回路/环,为了防止在遍历时一个顶点可能被访问多次,使用一个访问标记数组visited[]来标记顶点是否已经被访问过,若visited[]为1,则该顶点被访问过;
(2)首先访问起始顶点v,接着由v出发,依次访问v的各个未访问过的邻接顶点w1,w2,…,wi,将其入队;
(3)再依次访问w1,w2,…,wi的所有未被访问过的邻接顶点;
(4)再从这些访问过的顶点出发,再访问它们所有未被访问过的邻接顶点……依次类推,直到图中所有顶点都被访问过为止。
![](https://img-blog.csdnimg.cn/direct/4d39c1d360e948e3a79e84360ae8fc4f.png)
5.主函数部分main():
(1)用户输入1-5的数字,使用 switch 选择器选择调用CreateGraph()、Traverse()、CreatGraph()、ShowGraph()、createMGraph()、dfs()、bfs()函数来创建无向图、输出无向图、创建邻接表、输出邻接表、实现无向图的深度优先非递归遍历和广度优先遍历。
(2)用户输入6,完成程序:return 0;
四、功能模块程序流程图
1.菜单部分:
![](https://img-blog.csdnimg.cn/direct/474c1f50ab6c4542879690c51700361d.png)
2.采用邻接矩阵创建无向网 CreateGraph():
![](https://img-blog.csdnimg.cn/direct/95d9987e1f41401e9ec17872ab578800.png)
3.创建有向图的邻接表 CreatGraph():
![](https://img-blog.csdnimg.cn/direct/41536b3c36e1468f8ff26c0e27a44242.png)
4.邻接表深度优先非递归遍历 dfs():
![](https://img-blog.csdnimg.cn/direct/02bf87366b224e9b913b71a0241304dd.png)
5.无向图广度优先递归遍历 bfs():
![](https://img-blog.csdnimg.cn/direct/8d6a25631ba44d0bb44b5f635d5796fc.png)
五、实验结果
1.输入选项:1
![](https://img-blog.csdnimg.cn/direct/f738a6e3ffa647989b86bc7e1d575cb3.png)
2.输入选项:2和3
![](https://img-blog.csdnimg.cn/direct/37652b18612042d2a71806dd02540bbb.png)
3.输入选项:4
![](https://img-blog.csdnimg.cn/direct/5a43f25ce143405196165cc0e2f65262.png)
4.输入选项:5
![](https://img-blog.csdnimg.cn/direct/a040957f5ab54242934ba48b3793e11d.png)
5.输入选项:6
![](https://img-blog.csdnimg.cn/direct/e359300bf7cc43049f986266ef66108b.png)
六、算法分析
1.深度优先非递归遍历
(1)时间复杂度O(n):创建两个队列分别用于存储当前层和下一层结点,对n个结点需遍历n次,循环内各操作时间复杂度均为O(1),故总体时间复杂度为O(n);
(2)空间复杂度O(n):两队列最多需要存储n/2个结点数据,空间复杂度O(n)。
2.广度优先遍历
(1)时间复杂度O(|V| + |E|):其中 |V| 表示图中的顶点数,|E| 表示图中的边数,遍历每个顶点和每条边的时间复杂度均为 O(1),因此总时间复杂度为 O(|V| + |E|);
(2)空间复杂度O(|V| + |E|):在遍历过程中需要使用队列来存储每个被访问的顶点,而最坏情况下,所有顶点都被访问,每个顶点都需要加入队列一次,因此队列最大长度为 |V|,同时还需要额外的空间来存储邻接表,因此总空间复杂度为 O(|V| + |E|)。
七、操作说明
1.进入图系统后,会看见如下提示:
************************* 实验三 图 *************************
* *
* 1:创建无向图的邻接矩阵 *
* 2:创建有向图的邻接表 *
* 3:输出有向图的邻接表 *
*4:(邻接表存储)实现无向图的深度优先非递归遍历DFS遍历*
* 5:(邻接表存储)实现无向图的广度优先遍历 *
* 6:退出程序 *
* *
*************************************************************
☆请输入一个数字选项:
2.若想创建无向图的邻接矩阵,则输入1,然后输入顶点数、边数、点集,并逐条边输入对应的点和权值。
3.若想创建有向图的邻接表,则输入2,然后输入图的顶点数边数、点集,并逐条边输入对应的点和权值。
4.若想实现输出有向图的邻接表,则输入3。
5.若想实现采用邻接表存储实现无向图的深度优先非递归遍历,则输入4。
6.若想实现采用邻接表存储实现无向图的广度优先遍历,则输入5。
7.若想退出程序,则输入6。
八、源代码
#include<iostream>
#include<malloc.h>
#include <stack>
#include<queue>
#define MAX_VERTEX_NUM 100//顶点最大个数
#define MaxInt 36767//代表无穷
using namespace std;
//无向图邻接矩阵
typedef struct Graph /*定义图的邻接矩阵*/
{
int vexnum, arcnum; //图中顶点数目、边/弧的数目
char vexs[MAX_VERTEX_NUM]; //一维数组:存储顶点信息
int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //二维数组:存储顶点与顶点之间的关系
}graph;
//有向图邻接表
typedef char VerTexData;//顶点数据类型
typedef struct node//边节点
{
int dest;//目标节点位置
int cost;//边的权值
struct node* link;//下一边链接指针
}EdgeNode_youxiang;//表结点
typedef struct VerTexNode//顶点结点
{
VerTexData data;//顶点数据域
EdgeNode_youxiang* adj;//边链表头指针
}VerTexNode;//头结点
typedef struct AdjGraph//图的邻接表
{
VerTexNode VexList[MAX_VERTEX_NUM];//邻接表
int n, e;//顶点的实际数,边的实际数
}AdjGraph;
//无向图邻接表
struct Node// 边表结点
{
int adjves;//存储顶点的下标
struct Node* next;//连接下一个邻点
};
typedef struct Node EdgeNode;
typedef struct VertexNode//顶点表结点
{
int ves;//顶点的值
EdgeNode* firstedge;//相连的顶点的值
}VertexNode, AdjList[MAX_VERTEX_NUM];
typedef struct
{
AdjList adjlist;//邻接表
int ves;//顶点
int edge;//边
int book[MAX_VERTEX_NUM];//判断是否有被访问过
}MGraph;
void menu();
void CreateGraph(Graph& G);//创建无向网
int LocateVex(Graph& G, int v);//顶点的定位
void Traverse(Graph& G); //网的遍历
void CreatGraph(AdjGraph& G);//创建有向图邻接表
void ShowGraph(AdjGraph G);//输出邻接表
void createMGraph(MGraph* G);//创建无向图邻接表
void dfs(MGraph* G, int i);//无向图深度优先非递归遍历
void bfs(MGraph* G);//无向图广度优先递归遍历
void CreateGraph(Graph& G) {
cout << "顶点个数:";
cin >> G.vexnum;
cout << "边数:";
cin >> G.arcnum; //确定该网的顶点个数与边数
cout << "点集:";
for (int i = 0; i < G.vexnum; ++i) {
cin >> G.vexs[i]; //对顶点进行赋值
}
for (int i = 0; i < G.vexnum; ++i) {
for (int j = 0; j < G.vexnum; ++j) {
G.arcs[i][j] = MaxInt;//因为是网,所以先提前把每两个顶点之间的边的权值设为无穷大
}
}
cout << "构建无向网" << endl;
for (int k = 0; k < G.arcnum; ++k) {
char v1, v2;
int w;
cin >> v1 >> v2 >> w;//输入想要添加的边的权值以及边所依附的两顶点
int i = LocateVex(G, v1), j = LocateVex(G, v2);//确定两顶点在图中的位置
G.arcs[i][j] = w;
G.arcs[j][i] = G.arcs[i][j];//由于无向网具有对称性,因此可以对称赋值
}
}
int LocateVex(Graph& G, int v) {
for (int i = 0; i < G.vexnum; ++i) {
if (v == G.vexs[i]) return i;
}
return -1;
}
void Traverse(Graph& G) {
for (int i = 0; i < G.vexnum; ++i)
for (int j = 0; j < G.vexnum; ++j)
if (G.arcs[i][j] != MaxInt)
cout << "(" << G.vexs[i] << " " << G.vexs[j] << ") 权值:" << G.arcs[i][j] << endl;
}
//有向图邻接表
void CreatGraph(AdjGraph& G)//创建邻接表
{
int tail, head, weight;
cout << "输入图的顶点数和边数" << endl;
cin >> G.n >> G.e;
cout << "输入顶点:\n";
for (int i = 0; i < G.n; i++)//输入顶点信息
{
cin >> G.VexList[i].data;
G.VexList[i].adj = NULL;
}
cout << "逐条边输入,分别输入尾,头,权重:\n";
for (int i = 0; i < G.e; i++)
{
cin >> tail >> head >> weight;//逐条边输入
EdgeNode_youxiang* p = new EdgeNode_youxiang;
p->dest = head; p->cost = weight;
p->link = G.VexList[tail].adj;
G.VexList[tail].adj = p;
}
}
void ShowGraph(AdjGraph G)//输出邻接表
{
for (int i = 0; i < G.n; i++)
{
cout << G.VexList[i].data;
EdgeNode_youxiang* p = G.VexList[i].adj;
while (p != NULL)
{
cout << " -> (" << p->dest << "," << p->cost << ")";
p = p->link;
}
cout << "\n";
}
}
//无向图邻接表
void createMGraph(MGraph* G)
{
int i;
int start;
int end;
EdgeNode* e;
cout << "请输入顶点数与边数:" << endl;
cin >> G->ves >> G->edge;
//初始化
cout << "请输入各顶点:" << endl;
for (i = 0; i < G->ves; i++)//输入顶点
{
cin >> G->adjlist[i].ves;
G->adjlist[i].firstedge = NULL;
}
//创建邻接矩阵
cout << "请输入边:" << endl;;
for (i = 0; i < G->edge; i++)
{
cin >> start >> end;
e = (EdgeNode*)malloc(sizeof(EdgeNode));//分配空间
e->adjves = end;
e->next = G->adjlist[start].firstedge;
G->adjlist[start].firstedge = e;//类似于链表的前插
e = (EdgeNode*)malloc(sizeof(EdgeNode));//分配空间
e->adjves = start;
e->next = G->adjlist[end].firstedge;
G->adjlist[end].firstedge = e;//类似于链表的前插
}
}
//非递归深度遍历
void dfs(MGraph* G, int i)
{
stack <int>s;
EdgeNode* p;
memset(G->book, 0, sizeof(G->book));//清空标志位
G->book[i] = 1;
s.push(i);
cout << G->adjlist[i].ves;
p = G->adjlist[i].firstedge;
while (!s.empty())
{
p = G->adjlist[s.top()].firstedge;
while (p)
{
if (G->book[p->adjves] == 0)
{
G->book[p->adjves] = 1;
cout << G->adjlist[p->adjves].ves << " ";
s.push(p->adjves);
p = G->adjlist[p->adjves].firstedge;
}
else
p = p->next;
}
if (p == NULL)
{
s.pop();
}
}
}
//广度遍历
void bfs(MGraph* G)
{
int visited[MAX_VERTEX_NUM] = { 0 };
int i;
int k;
EdgeNode* p;
std::queue<int> q;
for (i = 0; i < G->ves; i++)
{
visited[i] = 0;
}
for (i = 0; i < G->ves; i++)
{
if (visited[i] == 0)
{
visited[i] = 1;
cout << G->adjlist[i].ves;
q.push(i);
while (!q.empty())
{
k = q.front();
q.pop();
p = G->adjlist[k].firstedge;
while (p)
{
if (visited[i] == 0)
{
visited[i] = 1;
cout << G->adjlist[p->adjves].ves <<" ";
q.push(p->adjves);
}
p = p->next;
}
}
}
}
}
void menu() {
cout << "\n";
cout << " ************************* 实验三 图 *************************\n";
cout << " * *\n";
cout << " * 1:创建无向图的邻接矩阵 *\n";
cout << " * 2:创建有向图的邻接表 *\n";
cout << " * 3:输出有向图的邻接表 *\n";
cout << " * 4:(邻接表存储)实现无向图的深度优先非递归遍历 *\n";
cout << " * 5:(邻接表存储)实现无向图的广度优先遍历 *\n";
cout << " * 6:退出程序 *\n";
cout << " * *\n";
cout << " *************************************************************\n";
}
int main()
{
graph ga;
int i, j;
int num;
AdjGraph G;
MGraph D;
MGraph B;
menu();
while (true)
{
cout << "☆请输入一个数字选项:";
cin >> num;
switch (num)
{
case 1: //创建无向图的邻接矩阵
{
cout<<"创建无向图的邻接矩阵:"<<endl;
CreateGraph(ga);
Traverse(ga);
cout << endl;
}break;
case 2://创建有向图的邻接表
{
cout << "创建有向图的邻接表:" << endl;
CreatGraph(G);
cout << endl;
}break;
case 3://输出有向图的邻接表
{
ShowGraph(G);
cout << endl;
}break;
case 4://无向图的深度优先非递归遍历DFS遍历
{
createMGraph(&D);
cout << "无向图非递归深度优先遍历结果:" << endl;
dfs(&D, 0);
cout << endl<<endl;
}break;
case 5://无向图的广度优先遍历
{
createMGraph(&B);
cout << "无向图非递归深度优先遍历结果:" << endl;
bfs(&B);
cout << endl<<endl;
}break;
case 6:
{
cout << "退出成功,欢迎下次使用!" << endl;
return 0;
}break;
default:
cout << "输入错误!请重新输入!" << endl;
}
}
}