基于邻接数组法的无向图实现

本文章对不带权的无向图作出实现(结点间的关系采用邻接数组 / 邻接矩阵表示),新人菜鸟写文章,难免有一些疏漏或者错误,如有发现,欢迎评论区指出,在此感谢各位看官老爷。

具体代码可以在主页获取。

如果对你有帮助,可以给卑微的博主留个赞、关注、收藏   (不是) 

目录

一.工具

二.抽象数据类型定义

三.存储结构定义

四.实现的基本操作定义

五.算法设计

六.运行测试


一.工具

以C/C++语言实现数据结构,编程环境Code::Blocks 17.12 ,使用C++提供标准容器queue和string ,整体使用c语言,但因为c语言的string需要采用char数组模拟,所以采用c++的string类型,为了实现方便,同时直接采用c++标准的队列,使代码更简洁,无需做一些额外操作。

在使用到c++的内容时会进行注释说明。

二.抽象数据类型定义

        

 以string类型为节点类型(VexType) , 采用邻接数组法,实现数据类型UD_Graph (无向图);

数据对象:Node={刘某,张某,李某.......∈ VexType} ;   (一些诗人的名字,string类型)

数据间关系:{Arcs[i] , i<图的边数e }   (Arcs[i]元素中有2个图节点,代表之间有通路)。

                                                        (Arcs数组在存储结构定义里可以找到)

三.存储结构定义

        宏定义

//(所有存储结构定义打包成头文件UD_Graph_ Structure.h)

#include <stdlib.h>
#include <stdio.h>
#include <queue>                 //包含C++标准的队列
#include <string>                //包含C++标准的字符串


#define UNVISITED  0            //节点未访问
#define VISITED    1			//节点已访问
#define OK         0			//函数执行成功
#define ERROR     -1			//函数执行失败
#define YES        1			//有朋友关系
#define NO     	   0			//无朋友关系
#define OVERFLOW   -2			//分配内存异常
#define VexType    string       //节点为字符串类型,存储姓名
#define MAXSIZE    100			//最大节点数目
#define Status 	   int			//状态为int型

using namespace std;			//c++语法 ,使用标准命名空间

Arcs数组定义

typedef struct 
{
  VexType v,w;       //代表v,m两个结点存在相连接的边
}Arcs;

无向图的存储结构定义

typedef struct
{
    VexType * vexs;      //存放顶点的数组,VexType为节点类型
    Status ** arcs;		//关系数组  arcs[1][3]==1代表 位序1和位序3的节点间有通路
    int n,e;			//图的节点数和边数
    int * tags;			//标记数组,用于遍历时判断是否节点已经访问
}UD_Graph;

四.实现的基本操作定义

注:在函数参数列表中,&代表的是引用型参数,不是取地址,使用&参数效果是:在函数中对

该参数的操作相当于操作原始数据。     (详细了解请查询百度)

1.初始化含n个顶点且无边的无向图G  

结果:成功返回0,否则返回-1

InitGraph(UD_Graph &G , VexType * vex , int n);

2.创建n节点,e条边的无向图G,vexs是节点信息,arcs是边信息   

结果:成功返回0,否则返回-1

CreateGraph(UD_Graph &G , VexType * vexs , int n , Arcs* arcs , int e);

3.销毁一个无向图G  (要求图G存在)

结果:成功返回0,否则返回-1

DestoryGraph(UD_Graph &G );

4.查找顶点v在无向图G中的位序  (要求图G存在)

结果:若图中存在v,返回v的位序,否则返回-1
Locate_vex(UD_Graph G ,VexType v);

5.无向图G位序为k顶点的值到w  (要求图G存在 )

结果:成功返回0,否则返回-1
   Get_vex(UD_Graph G,int k,VexType &w);

6.对无向图G的位序为k顶点赋值w  (要求图G存在 )

结果: 赋值成功返回0,否则返回-1

Put_Vex(UD_Graph G,int k ,VexType w);


7.无向图G中位序为k顶点的第一个邻接顶点的位序  (要求图G存在)

结果:若0 <= k < 图的节点数,且有邻接顶点,返回其位序,否则返回-1

First_AdjVex(UD_Graph G , int k);


8.m顶点为k顶点的邻接顶点,求无向图G中k顶点相对于m顶点的下一个邻接顶点的位序  (要求图G存在)

结果: 若0 <= k < 图的节点数 , 0 <= m < 图的节点数,在m之后还有k的邻接顶 点,返回其位序,否则返回-1 。
Next_Adjvex(UD_Graph G , int k , int m);

9.在无向图G中添加k到m的边  (要求图G存在)

结果:成功返回0,否则返回-1
  Add_Arc(UD_Graph &G,int k ,int m);

10.在无向图G中删除k到m的边  (要求图G存在)
   结果:成功返回0,否则返回-1
 Delete_Arc(UD_Graph &G,int k ,int m);

11.无向图G的深度优先遍历  (要求图G存在)
  结果:成功返回0,否则返回-1
DFSTraverse(UD_Graph G ,status(visit)(int) );

12.无向图G的广度优先遍历  (要求图G存在)

结果:成功返回0,否则返回-1
BFSTraverse(UD_Graph G ,Status(visit)(int) );

五.算法设计

//1.初始化操作


Status InitGraph(UD_Graph &G , VexType * vexs , int n)      
//传入实例化图变量G , 存放节点的数组vexs,节点数量n
{       int i , j;
	if(n<0 || n>MAXSIZE ||(n==0 && vexs==nullptr))		//传入参数不合理,直接结束
		return ERROR;

	G.n=n;
	G.e=0;
	if(n==0)
		return OK;

	if(nullptr== (G.vexs= new VexType[n]) )		
//为图的节点数组分配内存,由于节点是string类型,只能用new开辟空间
		return OVERFLOW;
	for(i=0;i<n;i++)
		G.vexs[i]=vexs[i];							//为图节点赋值

	if(nullptr== (G.arcs=(Status**)malloc(n*sizeof(Status*) ) ) ) 	
//为图的关系数组分配内存,n*n的数组,故采用二重指针
		return OVERFLOW;					//G.arcs指向存放n个指针的数组,
	for(i=0;i<n;i++)						//该数组每个元素G.arcs[i]都是一个 指向n个										          
                                            //Status(int)类型元素 的指针
	{
		if(nullptr== (G.arcs[i]=(Status*)malloc(n*sizeof(Status) ) ) )
			return OVERFLOW;
	}

	if(nullptr== (G.tags=(int*)malloc(n*sizeof(int) ) ) )		
//为图的标记数组分配内存,该数组用来存放 对应节点是否已访问
		return OVERFLOW;
	for(i=0;i<n;i++)
	{    G.tags[i]=UNVISITED;					//G.tags数组初始化为UNVISITED(未访问)
	     for(j=0;j<n;j++)
		  G.arcs[i][j]=NO;						//G.arcs数组初始化为NO(没有朋友关系)
	}

	return OK;
}

//2.创建无向图

Status  CreateGraph(UD_Graph &G , VexType * vexs , int n , Arcs* arcs , int e)	
//传入实例化图变量G,节点集合vexs,节点数量n,边集合arcs,边数量e
{
	if(ERROR==InitGraph(G,vexs,n) )					//初始化操作失败,直接返回ERROR
		return ERROR;
	if(e<0 || (e==0 && arcs!=nullptr) || (e>0 && arcs==nullptr) )		
//参数不合理直接返回ERROR
		return ERROR;

	G.e=e;
	int i;
	int v_num;									//v节点在图节点集合的位序
	int w_num;									//w节点在图节点集合的位序

	for(i=0;i<e;i++)
	{
		v_num=Locate_vex(G,arcs[i].v);	
//Locate_vex函数返回输入节点v在图G节点集合中的位序    后面会定义Locate函数
		w_num=Locate_vex(G,arcs[i].w);					
//Locate_vex函数返回输入节点w在图G节点集合中的位序
		if(v_num==ERROR || w_num==ERROR)				
//如果Locate_vex函数返回ERROR,直接返回ERROR
			return ERROR;
		G.arcs[v_num][w_num]=YES;		//数组元素G.arcs[v_num][w_num]赋值为YES,
		G.arcs[w_num][v_num]=YES;		//表示位序 v_num 和 w_num 的两节点有通路(朋友关系)
	}                                   //同时G.arcs[w_num][v_num]也需要赋值

	return OK;
}

//3.销毁无向图

Status  DestoryGraph(UD_Graph &G )						//传入无向图G
{
	delete []G.vexs;					//图的节点数组是string类型,释放需要用delete
	G.vexs=nullptr;								  //释放后指针置空,防止误操作

	int i;
	for(i=0;i<G.n;i++)
	{
		free(G.arcs[i]);						
                    //二重指针相当于指向一个 指针数组 的指针,需要先将数组中的指针释放
	}
        free(G.arcs);					 //释放数组中的指针后,释放指向该数组的指针
	G.arcs=nullptr;										//释放后指针置空,防止误操作

	G.e=0;													 //边数和节点数置零
	G.n=0;

	free(G.tags);													//释放标记数组
	G.tags=nullptr;									//释放后指针置空,防止误操作

	return OK;
}

//4.查找v节点在图G中的位序

int  Locate_vex(UD_Graph G ,VexType v)       		 //传入无向图G,要查询的节点v
{
	int i;
	for(i=0;i<G.n;i++)
		if(G.vexs[i]==v)			//遍历节点数组,找到v返回则位序,否则最后返回ERROR
			return i;

	return ERROR;
}

//5.获取无向图位序为k的节点到w

Status  Get_vex(UD_Graph G ,int k,VexType &w)					
//传入无向图G,要获取节点的位序k, w用来接收节点信息
{
	if(k>=G.n || k<0 )
		return ERROR;									 //参数不合理直接返回ERROR
	w=G.vexs[k];										//将图位序为k的节点赋值给w

	return OK;
}

//6.为无向图位序为k的节点赋值w

Status  Put_Vex(UD_Graph G ,int k ,VexType w)					
//传入无向图G,要赋值节点的位序k, w为想赋的值
{
	if(k>=G.n || k<0 )
		return ERROR;									//参数不合理直接返回ERROR
	G.vexs[k]=w;										//将w赋值给图位序为k的节点

	return OK;
}

//7.查找位序为k的节点 的第一个临接点 的位序

int  First_AdjVex(UD_Graph G , int k)				//传入无向图G,要查找节点的位序k
{
	if(k>=G.n || k<0 )
		return ERROR;									//参数不合理直接返回ERROR
	int i;
	for(i=0;i<G.n;i++)
		if(G.arcs[k][i]==YES)					//遍历位序为k的节点的 关系数组行,
			return i;						    //找到的话返回位序,否则最后返回ERROR
	return ERROR;
}

//8.查找无向图位序为k的节点 在位序m之后的第一个临接点

int     Next_Adjvex(UD_Graph G , int k , int m)					
//传入无向图G,要查找节点的位序k,要找的临接点的起始位序的前一位m
{
	if(k>=G.n || k<0 || m>=G.n-1 || m<0)
		return ERROR;									//参数不合理直接返回ERROR
	int i;
	for(i=m+1;i<G.n;i++)
		if(G.arcs[k][i]==YES)				//从m+1开始遍历位序为k的节点的 关系数组行,
			return i;						//找到的话返回位序,否则最后返回ERROR
	return ERROR;
}

//9.在无向图中增加 位序为k的节点 到 位序为m的节点 的边

Status  Add_Arc(UD_Graph &G , int k ,int m)	//传入无向图G,要添加边的节点位序k、m				
{
	if(k>=G.n || k<0 || m>=G.n || m<0)					 //参数不合理直接返回ERROR
		return ERROR;
	if(G.arcs[k][m]==YES || G.arcs[m][k]==YES)
	{
		cout << "已有此边,添加失败。" <<endl;	  //边已经存在,提示出错,返回ERROR
		return ERROR;
	}
	G.arcs[k][m]=YES;		   //将图的关系数组元素G.arcs[k][m]和G.arcs[m][k]赋值为YES
	G.arcs[m][k]=YES;
	G.e++;														 //图的边数加一

	return OK;
}

//10.在无向图中删除 位序为k的节点 到 位序为m的节点 的边

Status  Delete_Arc(UD_Graph &G , int k ,int m)					
//传入无向图G,要删除边的节点位序k、m
{
	if(k>=G.n || k<0 || m>=G.n || m<0)
		return ERROR;								    //参数不合理直接返回ERROR
	if(G.arcs[k][m]==NO && G.arcs[m][k]==NO)
	{										   //边不存在,提示出错,返回ERROR
		cout << "没有此边,删除失败。" <<endl;
		return ERROR;
	}
    
    G.arcs[k][m]=NO;		//将图的关系数组元素G.arcs[k][m]和G.arcs[m][k]赋值为NO					
	G.arcs[m][k]=NO;
	G.e--;														//图的边数减一

	return OK;
}

//11.连通子图的深度优先遍历  (递归实现)

Status  DFS_SubGraph(UD_Graph G,int i,Status(*visit)(string) )			
//传入无向图G,起始访问的节点位序i,访问节点所用函数visit
{
	int j;
	if(i>=G.n || i<0)
		return ERROR;									//参数不合理直接返回ERROR
	if( ERROR==visit(G.vexs[i]) )
		return ERROR;								//节点访问失败,直接返回ERROR
	G.tags[i]=VISITED;				  //将标记数组对应元素赋值为VISITED (表示已访问)

	for(j=First_AdjVex(G,i);j!=ERROR;j=Next_Adjvex(G,i,j) )			
//将位序为i的节点的 第一个临接点 的位序赋值给j
	{
		if(UNVISITED==G.tags[j] )					
//如果位序为j的节点还没访问,就对其进行子图的深度优先遍历
			if(ERROR==DFS_SubGraph(G,j,visit) )		  //如果遍历失败,直接返回ERROR
				return ERROR;

	}
		return OK;
}

//12.图的深度优先遍历 (递归实现)

由于无向图可能出现不连通的情况,这会导致从某个节点出发,只能访问到这个节点所在的连通子图,永远无法访问到图另外的连通子图,所以检查标记数组,看看还有没有节点未访问。

Status  DFSTraverse(UD_Graph G ,Status(*visit)(string) )			
//传入无向图G,访问节点所用函数visit
{
	int i;
	for(i=0;i<G.n;i++)
		G.tags[i]=UNVISITED;			  //标记数组都初始化为UNVISITED (表示未访问)
	for(i=0;i<G.n;i++)
	{
		if(G.tags[i]==UNVISITED)					//从0开始遍历标记数组,如果还没访问,
			if(ERROR==DFS_SubGraph(G,i,visit) )		//就进行当前位序的 子图深度优先遍历	                                                    
				return ERROR;					    //如果遍历失败返回ERROR
	}

	return OK;
}

//12.无向图的广度优先遍历  (非递归实现,使用队列)

Status  BFSTraverse(UD_Graph G ,Status(*visit)(string) )			
    //传入无向图G,访问节点所用函数visit
{
	int i,j,k;
	queue<int> Q;								//C++标准模板库的队列,队列存储int型
	for(i=0;i<G.n;i++)
		G.tags[i]=UNVISITED;			//标记数组都初始化为UNVISITED  (表示未访问)
	for(i=0;i<G.n;i++)
	{
		if(G.tags[i]==UNVISITED)	 //从0开始遍历标记数组,如果还没访问,就访问位序对应的节点				  
		{
			if(ERROR==visit(G.vexs[i])  )				//如果访问失败,直接返回ERROR
				return ERROR;
            G.tags[i]=VISITED;	  //将标记数组对应元素赋值为VISITED  (表示已经访问)
			Q.push(i);											    //将该位序入队
			while(Q.empty()!=true)									//如果队列非空
			{
				k=Q.front();									//获取队头位序到k
				Q.pop();										//队头出队
				for(j=First_AdjVex(G,k);j!=ERROR;j=Next_Adjvex(G,k,j) )	   	
                                    //将图位序为k 的第一个临接点位序赋值给j,
				{					//如果赋值成功,判断位序为j的节点是否已访问
					if(G.tags[j]==UNVISITED)				//如果没访问,就访问该节点
					{
						if(ERROR==visit(G.vexs[j]) )		//访问失败,直接返回ERROR
							return ERROR;
						G.tags[j]=VISITED;	//将标记数组对应元素赋值为VISITED (表示已访问)			
						Q.push(j);								  //该位序入队
					}
				}
			}

		}
	}
	return OK;
}

//14.节点访问函数 (visit)

Status VisitVex (VexType name)
{
    if(name.empty()==true)                 //string类型提供的判空函数,如果字符串为空,返回true
        return ERROR;
   cout<< name <<endl;			   								//c++语法,打印name的值
   return OK;                                                   //也可使用printf
}

六.运行测试

1.实例化一个图G , 创建一个6元素节点数组 ,对G进行初始化

预期结果:  节点数G.n=6   边数G.e=0   G.tags数组元素都是0 ,图的0号节点是李白 ,5号节点是王之涣

测试结果:

2.创建边集,包含2条边(李白与孟浩然,孟浩然与王之涣)

预期结果: 节点数G.n=6   边数G.e=2   G.tags数组元素都是0

图的0号节点是李白 ,5号节点是王之涣,关系数组[0][1]代表李白到孟浩然, 应该为1,

关系数组[0][3]代表李白到王昌龄,应该是0 ,关系数组[5][1]代表王之涣到孟浩然,应该是1

测试结果:

3.销毁图

预期结果:节点数G.n=0   边数G.e=0 , tags、vexs、arcs数组访问时报错

测试结果:

4.查找节点 李白,王昌龄,张三 在图中的位序

预期结果:李白在0号位序,王昌龄在3号,张三不在图中,为-1

测试结果:

5.获取位序为2,7,4的节点到s1,s2,s3

预期结果:2号位序是高适, 7号超出图的节点数,s2没有被赋值,4号位序是岑参

测试结果:

 

6.给位序为0,7的节点分别赋值 杜甫,张三

预期结果:0号节点原先是李白 , 赋值后是杜甫,7号节点超出节点数,赋值失败

测试结果:

7.查找位序为1 , 5 , 3节点的第一个临接点位序

预期结果:1号节点的第一个临接点是0号, 5号节点的第一个临接点是1号,3号节点没有临接点,输出-1

测试结果:

8.查找位序为0号节点在 位序1号以后的 第一个临接点,

查找位序为1号节点在 位序3号以后的 第一个临接点,

查找位序为4号节点在 位序0号以后的 第一个临接点。

预期结果:李白有到孟浩然和王昌龄的边,1号位序是孟浩然,之后的第一个是王昌龄3

                  孟浩然有到李白和王之焕的边,3号位序是王昌龄,之后的第一个是王之涣5

                  岑参没有到其他节点的边,0号位序是李白,之后找不到临接点,返回-1

测试结果:

9.原有李白到孟浩然,孟浩然到王之涣的边,添加李白到王昌龄、李白到位序为7号的节点、李白到孟浩然的边

预期结果: 李白到王昌龄添加成功,李白到7号节点添加失败,李白到孟浩然添加失败

测试结果:

10.删除1号节点到0号节点、1号节点到6号节点,2号节点到3号节点的边。

预期结果:原先李白到孟浩然有边,arcs[0][1]是1,删除后变0,

  6号节点超出允许范围,删除失败,

  2号到3号节点本身没有边,删除失败。

测试结果:

 11.遍历测试,该图有7个节点,分成2个连通子图

预期结果:

从0号节点开始进行子图深度优先遍历,应该输出 李白、孟浩然、王昌龄、岑参、王之涣、高适;杜甫无法访问到。

从6号节点开始进行子图深度优先遍历,应该输出杜甫,其他节点不能访问到。

深度优先遍历也是从0号节点开始遍历,所以遍历序列和 从0号节点开始子图的深度优先遍历相同,但是最后会检测tags数组,发现6号没有访问到,于是对6号进行子图深度优先遍历。

遍历序列:李白、孟浩然、王昌龄、岑参、王之涣、高适、杜甫。

广度优先遍历也是从0号节点开始遍历,遍历完一次后也是检测tags数组,发现6号未遍历,于是对6号节点进行广度优先遍历。

遍历序列:李白、孟浩然、高适、王昌龄、岑参、王之涣、杜甫。

测试结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

凛_Lin~~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值