无向图的创建 以及 图的深度优先遍历,并判断这个图是否是树

图的创建 以及 图的深度优先遍历,并判断这个图是否是树

看了这个标题,会发现我们有三个任务
①创建一个图
②深度优先遍历
③判断这个图是不是树

一.创建一个图

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字了,我写实验报告都没这么认真,哈哈哈哈哈

我还写了个广度优先遍历的算法
也可以看看
广度优先遍历
这两篇主要是在两个算法上有区别,剩下的都大同小异

希望各位程序员们能点个赞!

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值