图的存储结构和算法—创建,输出,遍历(DFS,BFS),销毁

1.图的存储结构

1.1 邻接矩阵存储方法

图的邻接矩阵是一种采用邻接矩阵数组表示顶点之间相邻关系的存储结构,设G=(V,E)是具有n(n>0)个顶点的图,顶点的编号依次为0~n-1,如果邻接矩阵A是n阶方阵,其定义如下:

在这里插入图片描述
图的邻接矩阵存储类型声明如下:

#define Max  100  //最大顶点个数
#define INF 32767  //定义 ∞ 
typedef struct   //图中顶点的类型声明 
{
	int no;			//顶点的编号 
	InfoType info; //顶点的其他信息,如权值,数据等
}ApexType;
typedef struct   //图的定义 
{
	int edges[Max][Max]; //邻接矩阵数组 
	int n,e;			//图的顶点数,边数 
	ApexType vexs[Max]; //存放所有顶点的数组 
}MatGraph;

邻接矩阵的主要特点:

1.一个图的邻接矩阵表示是唯一的。

2.特别适合于稠密图的存储。

1.2 邻接表存储方法

图的邻接表是一种顺序和链式存储相结合的存储方法,对于n个顶点的图,每个顶点建立一个单链表,将该顶点的所有邻接点链接起来。例如下图建立的邻接表:
在这里插入图片描述
在邻接表中包含两种类型的节点,一种为头结点,数量等于图的顶点数,另一种是边节点,也就是单链表中的节点。两种节点结构如下:

在这里插入图片描述

  1. 头结点组成:
    data:存储顶点的名称或者其他信息。
    firstnode:指向顶点所链接的单链表的首节点。

  2. 边节点组成:
    adj_edge:表示与该顶点邻接的其他顶点编号。
    nextarc:指向下一个边节点。
    weight:存储该边的权值或者其他信息。

图的邻接表存储类型声明如下:

#include<stdio.h>
#include<stdlib.h>
#define MaxSize 100
#define INF 32767
typedef struct EdgeNode //边节点类型 
{
	int adj_Edge; //邻接点的编号
	struct EdgeNode *next_arc;  //下一条边的指针
	int weight;  //该边的权值,可添加其他信息 
}EdgeNode;
typedef struct HeadNode //头结点类型 
{
	int data; //头结点的其他信息,可自由添加
	EdgeNode *first_Node;//指向第一个边节点 
}HeadNode;
typedef struct
{
	HeadNode adj_list[MaxSize];//头节点数组 
	int n,e;  //图的顶点数和边数 
}AdjGraph; 

2.图的基本算法

这里采用图的邻接表存储方法来存储图,以下均为邻接表的相关算法。

2.1 图的创建

根据邻接矩阵数组A,顶点个数n和边数e来创建图的邻接表G,算法如下:

void Create_Graph(AdjGraph *&G,int A[MaxSize][MaxSize],int n,int e)//图的创建 
{
	EdgeNode *p;
	G = (AdjGraph *)malloc(sizeof(AdjGraph));
	for(int i = 0;i < n;i++) //为邻接表中所有头结点的指针域置初值
	{
		G->adj_list[i].first_Node = NULL;
	}
	for(int i = 0;i < n;i++) //检查邻接矩阵的每个元素
	{
		for(int j = n-1;j >= 0 ;j--)
		{
			if(A[i][j] != 0 && A[i][j] != INF) //存在一条边
			{
				p = (EdgeNode *)malloc(sizeof(EdgeNode));
				p->adj_Edge = j;
				p->weight = A[i][j];
				p->next_arc = G->adj_list[i].first_Node; //采用头插法插入节点p
				G->adj_list[i].first_Node = p;
			}
		}
	}
	G->n = n;
	G->e = e;
}

2.2 图的输出

扫描邻接表G的头结点数组 adj_list ,对于每个单链表,先输出头结点的顶点信息,然后逐一输出单链表中所有节点的编号,算法如下:

void Display_Graph(AdjGraph *G) //输出图 
{
	EdgeNode *p;
	for(int i = 0;i<G->n;i++)
	{
		p = G->adj_list[i].first_Node;
		printf("%3d:",i);
		while(p != NULL)
		{
			printf("%3d[%d]->",p->adj_Edge,p->weight);
			p = p->next_arc;
		}
		printf("\n");
	}
}
int main()
{
	int A[MaxSize][MaxSize]={{0,8,INF,6,7},
				 			{5,0,3,1,INF},
				 			{INF,3,0,2,8},
				 			{6,1,2,0,9},
				 			{7,INF,8,9,0}};
	AdjGraph *G;
	int n = 5,e = 5;
	Create_Graph(G,A,n,e);
	Display_Graph(G); 
}

对于主函数中的邻接矩阵,输出函数执行结果如下:

在这里插入图片描述

2.3 图的遍历

从给定图中任意指定顶点出发,按照某种搜索方式沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次,这个过程成为图的遍历。以下介绍两种遍历方法。

2.3.1 深度优先遍历

深度优先遍历的过程是从图的某个初始点 v 出发,首先访问初始点 v,然后选择一个与 v 相邻且没有被访问过的顶点 w ,以 w 为初始点,再进行深度优先遍历,直到图中所有顶点都被访问为止,显然,这是一个递归过程。该遍历算法如下:

int visited[MaxSize] = {0};
//visited全局数组,用来标记顶点是否被访问,初始为 0,表示所有顶点均未被访问
void DFS(AdjGraph *G,int v)
{
	EdgeNode *p;
	visited[v] = 1;//从顶点v开始,标记为已访问
	printf("%d ",v);
	p = G->adj_list[v].first_Node;//指向v的第一个邻接点 
	while(p != NULL)
	{
		if(visited[p->adj_Edge] == 0)//第一个邻接点未被访问 
			DFS(G,p->adj_Edge);
		p = p->next_arc; //p指向v的下一个邻接点 
	} 
}

2.3.2 广度优先遍历

首先访问初始点 v,然后访问 v 的所有未被访问的邻接点 v1,v2,,vn ,然后按照 v1,v2,,vn 的顺序访问每一个顶点的未被访问的邻接点,直到图的所有顶点都被访问 。

在用广度优先遍历时需要使用一个队列,这里采用环形队列,算法如下:

typedef struct //环形队列 
{
	int data[MaxSize];
	int front,rear;
}SqQueue;

void InitQueue(SqQueue *&q)//队列初始化
{
	q = (SqQueue *)malloc(sizeof(SqQueue));
	q->front = q->rear = 0; 
}
bool enQueue(SqQueue *&q,int e)//入队列
{
	if((q->rear+1)%MaxSize == q->front)  //队满上溢出 
		return false;
	q->rear = (q->rear + 1)%MaxSize; 
	q->data[q->rear] = e;
	return true;
}
bool deQueue(SqQueue *&q,int &e)//出队列
{
	if(q->front == q->rear) //队空下溢出 
		return false;
	q->front = (q->front + 1)%MaxSize;
	e = q->data[q->front];
	return true;
}
bool QueueEmpty(SqQueue *q)
{
	return (q->front == q->rear);
}
void BFS(AdjGraph *G,int v)
{
	int w;
	EdgeNode *p;
	SqQueue *qu;           //环形队列指针 
	InitQueue(qu);		   //环形队列初始化 
	int visited[MaxSize];  //顶点访问标记数组
	for(int i = 0;i < G->n;i++)
		visited[i] = 0;
	printf("%d ",v);
	visited[v] = 1;
	enQueue(qu,v);
	while(!QueueEmpty(qu))
	{
		deQueue(qu,w);  //出队一个顶点w 
		p = G->adj_list[w].first_Node;//指向w的第一个邻接点
		while(p != NULL)
		{
			if(visited[p->adj_Edge] == 0) //当前邻接点未被访问 
			{
				printf("%d ",p->adj_Edge);  //访问该节点 
				visited[p->adj_Edge] = 1;   
				enQueue(qu,p->adj_Edge);  //该顶点进队 
			}
			p = p->next_arc;
		}
	}
} 

2.4 图的销毁

对于邻接表G,扫描其头结点数组指向的所有单链表,逐个释放单链表中的边节点,最后释放头结点数组,对应算法如下:

void Destory_Graph(AdjGraph *&G)//销毁图 
{
	EdgeNode *pre,*p;
	for(int i = 0;i < G->n;i++)
	{
		pre = G->adj_list[i].first_Node;
		if(pre != NULL)
		{
			p = pre->next_arc;
			while(p != NULL)
			{
				free(pre);
				pre = p;
				p = pre->next_arc;
			}
			free(pre);
		}
	}
	free(G);
}
  • 8
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值