图的创建 以及 图的深度优先遍历,并判断这个图是否是树
看了这个标题,会发现我们有三个任务
①创建一个图
②深度优先遍历
③判断这个图是不是树
一.创建一个图
1. 准备工作
在小学二年级学离散的时候我们都知道,图上结点之间的关系可以用邻接矩阵来表示
因此我们也可以用邻接矩阵来建立各个节点之间的联系
我采用的方法是将这个图封装成一个 struct 结点
这个邻接矩阵采用二维数组的方法来储存
其中这个图中需要储存每个结点的数据,邻接矩阵,顶点数,同时记录边数
之前的准备工作除了封装这个图结点以外
我们还要设定两个数组
一个用来记录点是否被访问过
一个用来记录边是否被访问过
#define Max 100
int v_visited[Max]={0}; //标记结点是否被访问过
int a_visited[Max][Max]={0}; //标记边是否被访问过
struct Graph
{
char a[Max]; //数据
int arcs[Max][Max]; //邻接矩阵
int vexnum; //顶点数
int anum; //记录边数
};
这里图中的每个结点的数据我们全部储存在 char a[]中,提取结点数据时直接输出相应结点对应下表的子符就行了
比如 V1 结点,对应的数据就是 a[1];
2.创建图的函数
2.1首先我们要输入这个图共有多少个结点,方便后面输入邻接矩阵
2.2输入邻接矩阵确定结点之间的关系
2.3按 V1到 Vn 输入数据,将这个数据传入 char a[ ]中
void CreateGraph(Graph *&graph)//创造邻接矩阵建立关系
{
int i,j;
cout<<"输入图的顶点数"<<endl;
cin>>graph->vexnum;//给的信息是图的顶点数a,则矩阵就是aXa的矩阵
graph->anum=0; //边数初始化
cout<<"输入邻接矩阵"<<endl;
for(i=1;i<=graph->vexnum;i++)
{
for(j=1;j<=graph->vexnum;j++)
{
cin>>graph->arcs[i][j];//输入各个信息 0/1
}
}
cout<<"按V1到Vn输入结点数据"<<endl;
for (int i = 1; i <= graph->vexnum; i++)
cin>>graph->a[i];
}
注意这里的图结点 graph 是引用,我们在后面还要用这个节点的引用
不然这个函数就没用了,这个和之前的二叉树一个道理
二.深度优先遍历
2.1
深度优先遍历我们可以用递归的思想来实现
这个函数传入的参数为 graph 结点,同样是引用
另一个参数是 结点,这里的结点我们用 int 型的 v,意思是第 v 个结点。比如 v 是 1,那传入的结点就是 v1结点
2.2
在准备工作的时候,我们有一个数据 int v_visited[Max]={0};
这个是用来记录结点是否被访问过,输出就代表被访问过了,如果以访问,这个值就变成 1
比如第 v 个结点被访问过了,那么 v_visited[ v ] = 1;
2.3
老师上课教的方法是用 firstadj 和 nextadj 两个函数来进行深度优先遍历
说实话,我自己写了一下这两个函数,没写出来
而且我觉得这样很麻烦,自己还要多写两个函数
因此我用了一种别的方法避免写这两个函数,也能做到深度优先遍历,代码量更少
void DFS(Graph *&graph, int v) //子图的深度优先遍历
{
cout<<graph->a[v]<<" ";
v_visited[v] = 1;
for (int i = 1; i <=graph->vexnum; i++)
{
if (graph->arcs[v][i] && !v_visited[i])
{
DFS(graph, i);
}
}
}
我们传入 v 结点后,输出 v 结点的数据,也就是第 v 个节点的数据,之后标记为被访问过
后面进行判断,看后面的结点有没有和这个 v 结点有边的,并且这个后面的结点还不能被访问过
有这样的结点的话,就对这个结点进行 DFS
举个例子
v1 结点先传入,输出,被标记
开始判断后面节点是否有边,并且未被访问
v2 与 v1有边,而且 v2 未被访问 (firstadj)
现在对 v2 进行深度遍历
v2 传入,输出,被标记
这个时候先判断的是 v2 与 v1 是由否有边
显然他们之间有边,但是 v1 已经被访问过了,因此他是不会在对 v1 进行深度遍历的
而是去找后面的结点有没有符合要求的,这样就避免了重复遍历而且陷入死循环的情况
也就避免了写那两个我不会的函数
当然也可能会有人问,如果 v1 除了 v2 这个结点还有别的结点怎么办
当 v2 这个子图全部遍历完了以后,注意那个for循环,这个时候 i 就是 2 以后的数字,这个时候判断的是 v1 与 v3 4 5 有没有边,有的话继续像 v2 那样进行深度遍历,这样也就达到了 nextadj 的效果
这个函数中 !v_visited[i]
是关键
2.4
仔细看的话你会发现我在这个函数后面写了一个注释
//子图的深度优先遍历
为什么这么说
因为你会发现如果这个图是个非连通图的话,就只能遍历一个子图,剩下的部分不能遍历到
因此我们就要写一个循环,先把他们所有的点都 未被访问 化,也就是把 v_visited[ ]全弄成0
再写一个循环,把每个结点都遍历一次,如果一个结点没有被访问过(非连通图中,第二个子图和第一个子图没有联系,也就不能北方问到),就对他进行深度遍历
这样我们就把每个子图都访问到了
void travel_dfs(Graph *&graph) //完整图的深度优先遍历
{
for (int i=1; i<=graph->vexnum; i++)
v_visited[i]=0;
for (int i=1; i<=graph->vexnum; i++)
{
if(!v_visited[i])
DFS(graph,i);
}
}
这样他才是一个完整的深度优先遍历
主函数只调用这个travel_dfs
就行了
三.用深度优先遍历判断这个图是不是树
3.1
小学二年级我们在离散课上又学过如何判断一个图是不是树
如果一个图的 边 = 结点树 - 1,那么他就是树
因此我们要来求这个图的边
在深度遍历的函数中,我们发现只要两个结点之间有边 ,我们就给 graph 中的 anum 数加一
所以这个函数和深度优先遍历的内容差不多,只是稍有差异
void DFS_tree(Graph *&graph,int v) //子图的深度优先遍历
{
for (int i = 1; i <=graph->vexnum; i++)
{
if (graph->arcs[v][i] && !a_visited[v][i]&&!a_visited[i][v])
{
(graph->anum)++;
a_visited[v][i]=1;
a_visited[i][v]=1;
DFS_tree(graph,i);
}
}
}
在准备工作中,我们还设定了一个记录边是否被访问的数据
在判定条件那里,我们改成了if (graph->arcs[v][i] && !a_visited[v][i]&&!a_visited[i][v])
如果两个结点之间有边,而且这个边都没有被访问过
这里没有被访问过的意思是 v 到 i 没有访问,i 到 v 也没有被访问过
这样就避免了边重复计算的情况
至于为什么不用if (graph->arcs[v][i] && !v_visited[i])
的判断方法了
我们来举个例子
当我们遍历到4的时候,这一切都是正常的,1-2 2-3 3-4的边都记录了
但是再往后遍历的时候,3被访问过了,2也被访问过了,但是4-2这条边没有被记录
就漏掉了一条边
但是用if (graph->arcs[v][i] && !a_visited[v][i]&&!a_visited[i][v])
就没有这个问题
4-2 2-4这条边都没有被访问过,这样我们就记录上这条边了
3.2
同样上面那个函数只是子图的遍历求边
我们还需要对整个图进行遍历
void Anum(Graph *&graph) //完整图的边数
{
for (int i=1; i<=graph->vexnum; i++)
v_visited[i]=0;
for (int i=1; i<=graph->vexnum; i++)
{
if(!v_visited[i])
DFS_tree(graph,i);
}
}
3.3
最后这个函数我们要来对这个图是否是数来进行判断
已知边数,节点数
用小学二年级的离散知识来判断是否是数
bool istree(Graph *&graph) //判断是否为树
{
Anum(graph);
cout<<"这个图的边数为 :";
cout<<graph->anum<<endl;
if(graph->anum==graph->vexnum-1)
cout<<"这个图是树";
else
cout<<"这个图不是树";
}
我在这里加了一个输出边数的语句
四.主函数
我们用节点创建的图,需要向系统申请一个空间
这里用到malloc函数,用来申请空间
同时要用到头文件库#include<malloc.h>
int main()
{
Graph *graph;
graph=(Graph*)malloc(sizeof(Graph));
CreateGraph(graph);
cout<<"遍历结果是 : "<<endl;
travel_dfs(graph);cout<<endl;
istree(graph);
free(graph);
}
最后记得把这个空间释放掉,用free()函数
五.完整代码
#include<iostream>
#include<malloc.h>
using namespace std;
#define Max 100
int v_visited[Max]={0}; //标记结点是否被访问过
int a_visited[Max][Max]={0}; //标记边是否被访问过
struct Graph
{
char a[Max]; //数据
int arcs[Max][Max]; //邻接矩阵
int vexnum; //顶点数
int anum; //记录边数
};
void CreateGraph(Graph *&graph)//创造邻接矩阵建立关系
{
int i,j;
cout<<"输入图的顶点数"<<endl;
cin>>graph->vexnum;//给的信息是图的顶点数a,则矩阵就是aXa的矩阵
graph->anum=0;
cout<<"输入邻接矩阵"<<endl;
for(i=1;i<=graph->vexnum;i++)
{
for(j=1;j<=graph->vexnum;j++)
{
cin>>graph->arcs[i][j];//输入各个信息 0/1
}
}
cout<<"按V1到Vn输入结点数据"<<endl;
for (int i = 1; i <= graph->vexnum; i++)
cin>>graph->a[i];
}
void DFS(Graph *&graph, int v) //子图的深度优先遍历
{
cout<<graph->a[v]<<" ";
v_visited[v] = 1;
for (int i = 1; i <=graph->vexnum; i++)
{
if (graph->arcs[v][i] && !v_visited[i])
{
DFS(graph, i);
}
}
}
void travel_dfs(Graph *&graph) //完整图的深度优先遍历
{
for (int i=1; i<=graph->vexnum; i++)
v_visited[i]=0;
for (int i=1; i<=graph->vexnum; i++)
{
if(!v_visited[i])
DFS(graph,i);
}
}
void DFS_tree(Graph *&graph,int v) //子图的深度优先遍历
{
for (int i = 1; i <=graph->vexnum; i++)
{
if (graph->arcs[v][i] && !a_visited[v][i]&&!a_visited[i][v])
{
(graph->anum)++;
a_visited[v][i]=1;
a_visited[i][v]=1;
DFS_tree(graph,i);
}
}
}
void Anum(Graph *&graph) //完整图的边数
{
for (int i=1; i<=graph->vexnum; i++)
v_visited[i]=0;
for (int i=1; i<=graph->vexnum; i++)
{
if(!v_visited[i])
DFS_tree(graph,i);
}
}
bool istree(Graph *&graph) //判断是否为树
{
Anum(graph);
cout<<"这个图的边数为 :";
cout<<graph->anum<<endl;
if(graph->anum==graph->vexnum-1)
cout<<"这个图是树";
else
cout<<"这个图不是树";
}
int main()
{
Graph *graph;
graph=(Graph*)malloc(sizeof(Graph));
CreateGraph(graph);
cout<<"遍历结果是 : "<<endl;
travel_dfs(graph);cout<<endl;
istree(graph);
free(graph);
}
六.测试结果
1. 树
邻接矩阵
0 1 0 0 1 1
1 0 1 1 0 0
0 1 0 0 0 0
0 1 0 0 0 0
1 0 0 0 0 0
1 0 0 0 0 0
2.不是树
邻接矩阵
0 1 0 0 1 1
1 0 1 1 1 0
0 1 0 0 0 0
0 1 0 0 0 0
1 1 0 0 0 1
1 0 0 0 1 0
3.非连通图
邻接矩阵
0 1 1 0 0
1 0 0 0 0
1 0 0 0 0
0 0 0 0 1
0 0 0 1 0
这文章加起来7000字了,我写实验报告都没这么认真,哈哈哈哈哈
我还写了个广度优先遍历的算法
也可以看看
广度优先遍历
这两篇主要是在两个算法上有区别,剩下的都大同小异