数据结构-图

part 8 图(略)

假如你要周游中国, 现在在湖北,周边有安徽,江西,湖南,重庆,陕西,河南等城市。你下一步怎么走比较划算?

image-20210322181850780

 

1 图的概念

1.1 背景

现实生活中,社交,地图导航等多对多的情况。

 

1.2 定义

图由顶点和边构成,表示为G(V,E)。

G:表示一个图

V:是图中顶点的有穷非空集合

E:是图G中边的集合

顶点:线性表中数据称为元素,树中数据 称为结点,图中数据称为顶点。

存在空表,空树,但图结构中,不允许没有顶点

边:顶点之间的逻辑关系,边集可以为空(两顶点之间没有关系)

1.3 分类

1.3.1 无向图

image-20210322190423528

表示:G_1={ (1,2),((1,5),(1,6),(2,5),(2,3),(5,4),(5,6),(3,4),(4,6)};

无向完全图

任意两个顶点之间都存在边

含有n个顶点的无向完全图有条边 (组合问题)

image-20210322192114230

 

1.3.2 有向图

image-20210322190727611

表示方法:

= {<B,A>,<B,C>,<A,D>,<C,A> };

有向完全图

image-20210322192304713

 

n个顶点的有向图,有n*(n-1)条边。

1.3.3 简单图

在图中,不存在顶点到其自身的边,且同一条边不重复出现,称这样的图为简单图

 

image-20210322191227564

 

image-20210322191326863

 

1.3.4 网

权:与图的边相关的数叫做权

带权的图称为网

image-20210322193015948

2 图的存储结构

2.1 邻接矩阵

顶点不分大小,采用一维数组存储

边是顶点与顶点之间的关系,采用二维数组存储

image-20210322194524726

 

2.1.1 顶点数组

 

2.1.2 边数组

2.1.3 助解

主对角线:全为0

值:

 无边:arc[1][3]=0  因为B到D的边不存在
 有边:arc[2][0]=1  因为C到A的边存在

对称矩阵:

 由于是无向图,B->D的边不存在,D->B的边也不存在,所以 
   若:arc[i][j]=0 ,可得arc[j][i]=0
   同理:A->B的边存在,B->A的边也存在,所以
   若arc[i][j]=1 则arc[j][i]=1成立

2.1.4 结论

(1)顶点的度:这个顶点在邻接矩阵中第i行或者第i列的元素 之和

(2)找顶点的所有邻接点,将矩阵第i行元素扫描一遍,若值为1,则是邻接点

2.2 有向图

image-20210322200326662

$$\begin{matrix}
\\
&&A_0&B_1&C_2&D_3\\
\end{matrix}
\\
\begin{matrix}
A_0\\
B_1\\
C_2\\
D_3\\
\end{matrix}
\begin{bmatrix}
0&0& 0&1\\
1&0&1&0\\
1&1&0&0\\
0&0&0&0
\end{bmatrix}$$

有向图的顶点的度

入度:(指向顶点的)矩阵中所在列之和

出度:(顶点出发)矩阵中所在行之和

查找邻接点:遍历第i行元素

2.3 网

表示计算机允许的,大于所有边上的权值(一个不可能的极限值)(两个数据无关)

0表示:不存在顶点到自身的边(i==j)

 

image-20210322200912221

2.4 邻接矩阵实现

2.4.1 存储结构

 typedef  char  ver_tex_type;//顶点类型
 typedef  int   edge_type;//边上的权值类型
 #define MAXVEX   100 //最大顶点数
 #define  INFTY   65535//采用65535代表无穷大
 ​
 typedef  struct
 {
     ver_tex_type vex[MAXVEX];//顶点表
     edge_type  arc[MAXVEX][MAXVEX];//邻接矩阵
     int  num_vex,num_edge;//图中当前顶点数,和边数
 }mgraph;

2.4.2 邻接矩阵的创建无向网

(1)算法步骤

 (1)输入总顶点数和总边数
 (2)依次输入点的信息存入顶点表中
 (3)初始化邻接矩阵,使每个权值初始化为极大值
 (4)构造邻接矩阵。
     依次输入每条边依附的顶点,和其权值。确定两个顶点在图中位置后,使相应边赋予相应的权值,同时使其对称边赋予相同的权值。
 ​

(2)算法实现

  status cteate_mgraph(mgraph *g )
 {
     //输入总顶点数,总边数
     printf("输入顶点数和边数:\n");
     scanf("%d %d",&(g->num_vex),&(g->num_edge));
     //顶点表:一维数组的获取
     int i;
     for(i=0;i<g->num_vex;i++)
         scanf("%c",&(g->vex[i]));
     
    //初始化邻接矩阵为无穷大
     int j;
     for(i=0;i<g->num_vex;i++)
         for(j=0;j<g->num_edge;j++)
             arc[i][j]=INFTY;
     //输入每个边权
     int k;
     for(k=0;k<g->num_edge;k++)
     {
         printf("输入边(vi,vj)上的下标i,j和权w:")
         scanf("%d %d %d",&i,&j,&w);
         g->arc[i][j]=w;//无向图,对称矩阵
         g->arc[j][i]=w;
     }
     
 }

(3)算法分析

时间复杂度O(n+n^2 +e)---->为O(n^2)

2.4.3 构造无向图

(1) 初始化邻接矩阵的值为0

(2)构造邻接矩阵时,修改权值w为1

2.4.4 小结

优点:

(1)便于判断两个顶点之间是否右边

(2)便于计算各个顶点的度

对于 有向图,第i行是其顶点的出度

第i列是其顶点的入度

缺点:

(1)不便于增加和删除顶点

(2)不便于统计边的数据,需要 遍历邻接矩阵所有元素 才能统计完毕,时间复杂度为O(n^2)

(3)空间复杂度高

 

3 邻接表

邻接表是图 的一种链式存储结构。

 

image-20210322194524726

 

表示方法

image-20210323114503278

处理办法:

(1)顶点表:一维数组存储

顶点元素:data :数据元素

firstarc:存储指向第一个邻接点的指针,便于查找该顶点的边信息

image-20210323114620605

(2)边表:由于邻接点的个数不定,所以采用单链表存储

边表结点:adjvex :邻接点域,存储该邻接点在顶点表中的下标

info :数据域(边权)

nextarc:链域(与顶点邻接的下一条边的结点地址)

无向图称为顶点的边表

有向图称为顶点的出边表(顶点为弧尾),可求顶点出度

逆邻接表:以顶点为弧头,可求顶点入度

 

3.1 邻接表存储结构

 #define  MAXVEX 100
 typedef  char ver_tex_type;//顶点类型
 typedef  int  edge_type;//边权
 typedef   struct  edge_node
 {
     int adjvex;//邻接点域
     edge_type weight;//权值,非网图可以不需要
     strcut edge_type *next;//链域,指向下一个邻接点
 }edge_node;
 ​
 typedef struct  ver_tex_node//顶点结点
 {
     ver_tex_node data;//顶点域
     edge_node  * firstarc;//边表头指针
 }ver_tex_node,adj_list[MAXVEX];//邻接表元素,邻接表类型
 ​
 typedef struct //边表的集合
 {
     adj_list arr_list;//元素是邻接表 
     int num_vex,num_edge;
 }graph_adj_list;
 ​

 

3.2 邻接表的建立---无向图

3.2.1 算法步骤

 (1)输入总顶点数和总边数
 (2)有依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL
 (3)创建邻接表。
         依次输入每条边依附的两个顶点,确定顶点序号i和j后,将此边结点分别插入vi和vj对应的两个边链表头部

 

3.2.2 代码实现

 status create_al_graph(graph_adj_list g)
 {
     int i,j,k;
     edge_node *e;
     //顶点数和边数
     printf("输入顶点数和边数:");
     scanf("%d%d",&(g->num_vex),&(g->num_edge));
     //建立顶点表
     for(i=0;i<g->num_vex;i++)
     {
         scanf("%c",&(g->arr_listp[i].data));//输入顶点数据域信息
         g->arr_list[i].firstarc=NULL;//边表置空
     }
     
     //建立边表
     for(k=0;k<g->num_vex;k++)
     {
         printf("输入边(vi,vj)的端/顶点:\n");
         scanf("%d %d",&i,&j);
         //将此边结点插入vi对应的边链表头部
         //无向图:一条边两个端点,循环中,针对两个端点i,j,进行插入
         e=(edge_node *)malloc(sizeof(edge_node));//申请边表结点空间
         e->adjvex=j;//邻接序号为j;
         //头插法。插入结点e
         e->next=g->arr_list[i].firstarc;//结点指向当前顶点指向的结点
         g->arr_list[i].firstarc=e;//
         
         //将此边结点插入vj对应的边链表头部
         e=(edge_node *)malloc(sizeof(edge_node));//申请边表结点空间
         e->adjvex=i;//邻接序号为i;
         e->next=g->arr_list[j].firstarc;//结点指向当前顶点指向的结点
         g->arr_list[j].firstarc=e;//
     }
 }

 

3.3 小结

优点

(1)便于增加和删除顶点

(2)便于统计边的数目,按顶点表顺序扫描所有边表,可得到边的数目,时间复杂度 O(n);

(3)空间效率高

缺点

(1)不便于判断两点之间是否存在边

(2)不便于计算图的度,尤其是有向图的入度。

4 图的遍历

4.1 深度优先搜索(DFS)

depth first search

image-20210323161309951

步骤:

(1)任选一顶点,如V1

(2)选择一个邻接点,一路扎到底

(3)返回到未被访问得其他结点,再一路扎到底

 

4.2 深度优先搜索遍历

 

4.2.1 邻接矩阵的遍历

算法步骤(略)

(1)从图中某个顶点v出发,访问v,并置visited[i]的值为true
(2)依次检查v的所有false,再从w出发进行递归遍历,直到图中所有顶点都被访问过
深度优先算法
#include<stdbool.h>//TRUE和FALSE 
_bool  visited[MAX];//访问标志的数组

//从第v个顶点出发,递归,深度优先 搜索
void   dfs_mg(mgraph *mg,int v)
{
	//置访问标志数组相应下标的元素为true
	visited[v]=TRUE;
	//打印结点
	printf("%c\t",g->vex[i]);//打印顶点数据
	
	int j;
	for(j=0;j<g->num_vex;j++)//依次遍历行
		if(g->arc[v][j]!=0 && !visit[j])
		//g->arcs[v][j]!=0    表示j是v的邻接点,即v->j
		//!visited[j];//j没有被访问过
			dfs_mg(g,w);	//扎到底,找到有关V的邻接点	
}

//遍历图g
//另选其他未被访问的顶点作为起始点重复上述深度优先搜索过程
void  dfs_travel(mgraph *g )
{
    int i;
    //初始化所有顶点状态为未访问
    for(i=0;i<g->num_vex;i++)
        visited[i]=FALSE;
    
    for(i=0;i<g->num_vex;i++)
        if(!visited[i])
            dfs_mg(g,i);
}

 

4.2.2 邻接表的dfs

_bool  visited[MAX];
void dfs_al(alg *gl,int v)
{
	edge_node *p;//边表结点指针
	visited[v]=TRUE;//已访问
	printf("%c",gl->arr_list[i].data);//打印顶点信息
	p=gl->arr_list[i].firstarc;//从第一个邻接点开始
	while(p)
	{
    	if(!visited[p->adjvex])//顶点位置域未访问
    		dfs_al(gl,p->adjvex);//继续访问
    		p=p->next;
	}
}

//从其他未被访问的结点开始
void  afs_al_travel(alg *gl)
{
	int i;
	for(i=0;i<gl->num_vex;i++)
		visited[i]=FALSE;
	for(i=0;i<gl->num_vex;i++)
		if(!visited[i])//若是连通图,则只访问依次,例如丢手绢
			dfs_al(gl,i);
}

 

4.3 广度优先遍历

breadth_first_search

 

类似于树的层次遍历(从上到下,从左到右)

(1)从某个顶点v出发,访问v

(2)依次访问v的邻接点

(3)依次访问v的邻接点的邻接点

(4)重复,直到所有结点都被访问

image-20210323161309951

image-20210323171940103

 

4.3.1 邻接矩阵

先进先出----》队列

算法步骤

(1)从图中找到某个顶点V出发,访问V,并置visited[v]值为TRUE
(2)只要队列不空,进行如下操作
			·队列顶点u出队
			·依次检查u的所有邻接点j,如果visited[j]的值为FALSE,则访问j;并置visited[j]的值为true,然后将j进队
typedef  char  elem_type;
typedef  struct
{
	elem_type  data[MAXVEX];
	int front;//队头
	int rear;//队尾
}sq_que;

void bfs_travel(mgraph *mg ,int v)
{
	int i,j;
	sq_que q;
	init_que(&q);//队列初始化
	//访问标志的数组置0
	for(i=0;i<mg->num_vex;i++)
		visited[i]=FALSE;
	
	for(i=0;i<mg->num_vex;i++)//对每个顶点做循环
	{
		if(!visited[i])//如果未访问
		{
			visited[i]=TRUE;//更新访问状态
			printf("%c",mg->vex[i]);//打印顶点值
			en_que(&q,i);//顶点位置域入队
			while(q->front!=q->rear)//非空
			{
				de_que(&q,&i);//出队,赋值给i
				for(j=0;j<mg->num_vex;j++)//检查i的邻接点
				{
				//判断是否为邻接点,且未访问过;
					if(mg->arc[i][j]==1 && !visited[j])
						{
							visited[j]=TRUE;//更新访问状态
							printf("%c",mg->vex[j]);//打印顶点
							en_que(&q,i);//入队
						}
				}
			}
		}	
	}
}

//初始化队列
init_que(sq_que *q)
{
	q->front=0;
	q->rear=0;
}
	//入队
status en_que(sq_que *q,elem_type e)
{
	if((rear+1)%MAXVEX== front)
		return ERROR;
		q->data[q->rear]=e;
		q->rear++;
		return OK;
}
status  de_que(sq_que *q,elem_type *e)
{
	if(q->front == q->rear)//空队
		return ERROR;
		*e=q->data[q->front];
		q->front++;
		return OK;	
}

 

邻接表的实现----待补

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值