Data Structures(五) 图

本文详细介绍了图的基本概念,包括无向图、有向图、简单图、完全图等,以及图的存储结构如邻接矩阵、邻接表和十字链表。此外,深入探讨了图的两种遍历方法:深度优先遍历和广度优先遍历,提供了具体的算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、图的定义:

图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),G是一个图,V是图中的顶点集合,E是图中边的集合。

注意:

1、线性表中把数据元素叫做元素,树中叫做结点,图中则称为顶点。

2、线性表中没有元素为空表;树中没有结点,叫空树;在图中,不允许没有顶点,强调了顶点集合的有穷非空。

3、线性变相邻元素具有线性关系;树的上下两层具有层次关系;图的任意两个顶点之间的关系可以用边来表示。

各种图的定义:

无向边:图中两个顶点之间的连线没有方向就称为无向边,可以用(A,D)(D,A)无需对偶表示。如果任意两点的边都是无向边,图为无向图

有向边:图中v1到v2顶点之间的边有方向,这条边也叫弧,用有序对偶<v1,v2>,v1是弧尾,v2是弧头。如果任意两点的边都是有向边,图为有向图

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

无向完全图:无向图中任意两个顶点之间都存在边,则为无向完全图。n个顶点有n*(n-1)/2条边。

有向完全图:有向图中任意两个顶点之间都存在弧,则为有向完全图。n个顶点有n*(n-1)条边。

图中顶点与边的关系:

对于无向图,顶点v的度是和v相关联的边的数目。对于有向图,以顶点v为头的弧的数目称为v的入度;以v为尾的弧的数目为v的出度。

图中一个顶点到另一个顶点的路径是不唯一的,路径的长度为路径上边或弧的数目。

第一个顶点和最后一个顶点相同的路径称为回路或环。顶点不重复出现的路径称为简单路径。除第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,叫做简单回路。

连通图:

如果图中任意两个顶点都有路径(都是连通的)则称图为连通图。

无向图中的极大连通子图称为连通分量。

有向图中任意两个顶点都有<A,B><B,A>,则称图为强连通图。极大联通分量称为强连通分量。

二、图的存储结构

1、邻接矩阵

用一个一维数组存储顶点;用一个二维数组存储边存在就为1,不存在就为0。所以无向图的边数组是一个对称矩阵。

有了这个矩阵,就可以知道任意两点是否有边;知道某个顶点度就是顶点Vi在矩阵中第i行或列的元素之和;求顶点Vi的所有邻接点就是矩阵中的第i行元素扫描一遍,为1就是邻接点。

#pragma once
typedef char VertexType;//顶点类型可以自己定义
typedef size_t EdgeType; //边的权值类型
#define MAXVEX 20 //最大顶点数

//邻接矩阵的结构定义
typedef struct MGraph
{
	VertexType vexs[MAXVEX];
	EdgeType arc[MAXVEX][MAXVEX];
	int numVertexs, numEdges;
}MGraph;

//创建无向图的邻接矩阵
void CreateMGraph(MGraph *g)
{
	cout << "输入顶点数和边数";
	cin >> g->numVertexs;
	cin >> g->numEdges;
	for (size_t i = 0; i < g->numVertexs; i++)
	{
		cin >> g->vexs[i];
	}
	for (size_t i = 0; i < g->numVertexs; i++)
	{
		for (size_t j = 0; j < g->numVertexs; j++)
		{
			g->arc[i][j] = 0;
		}
	}
	int i, j, w;
	for (size_t k = 0; i < g->numEdges; k++)
	{
		cout << "输入边(vi,vj)上的下标i,下标j,和权值w";
		scanf("%d %d %d", &i, &j, &w);
		g->arc[i][j] = w;
		g->arc[i][j] = g->arc[j][i];
	}
}

2、邻接表

对于边数相对顶点较少的图,对存储空间造成大量浪费。因此可以考虑把弧或边用链表来存储。

(1)图中的顶点用一维数组存储。不过每个数组还元素需要一个指向第一个邻接点的指针。

(2)每个顶点vi和它的邻接点构成一个单链表,无向图称为顶点Vi的边表,有向图称为顶点Vi作为弧尾的出边表。

顶点表的各个节点是由data域和firstedge指针域构成firstedge指向边表的第一个节点。边表节点是由adjvex(邻接点域,存储该节点在顶点表的下标)和next指向下一个节点的指针。

对于有向图,以顶点为弧尾存储边表。

//邻接表的结构定义

//顶点表的结构定义
typedef struct VertexNode
{
	VertexType data;
	EdgeNode* firstEdge;//边表头指针
}VertexNode;

//边表结点的结构定义
typedef struct EdgeNode
{
	int adjvex;        //邻接点域,存储该顶点的下标
	EdgeType w;        //权值
	EdgeNode* next;    //下一个邻接点
}EdgeNode;

typedef struct
{
	VertexNode *adjList;
	int numVertex, numEdge;
}GraphAdjList;

3、十字链表---有向邻接表的优化

结合邻接表,重新定义顶点表和边表结点。

//十字链表的结构定义

//顶点表的结构定义
typedef struct Cross_VertexNode
{
	VertexType data;
	Cross_EdgeNode *firstIn, *firstOut;   //入边表和出边表的第一个节点
}VertexNode;

//边表结点的结构定义
typedef struct Cross_EdgeNode
{
	int tailvex;            //弧起点在顶点表的下标
	int headvex;            //弧终点在顶点表的下标
	EdgeType w;             //权值
	EdgeNode* headlink;     //终点相同的下一条表
	EdgeNode* taillink;     //起点相同的下一条表
}EdgeNode;

typedef struct
{
	Cross_VertexNode *adjList;
	int numVertex, numEdge;
}GraphCrossAdjList;

 

三、图的遍历

从图的某一顶点出发,访问图中其余顶点,且每个顶点仅被访问一次。

1、深度优先遍历  DepthFirstSearch  是一个递归的过程 

用邻接矩阵实现。

从顶点A开始,走过以后做上标记,根据右手原则访问它的邻接点,若该顶点没有未访问过的邻接点,就退回上一个顶点,知道所有顶点都被访问一遍。

bool visited[MAXVEX];//访问标志的数组
//深度优先访问顶点的递归算法
void DFS(MGraph G, int i)
{
	cout << G.vexs[i];     //如果该顶点没有被访问,则打印该顶点
	visited[i] = true;     //标志设成已访问过
	for (size_t j = 0; j < G.numVertexs; j++)
	{
		//访问存在的边表并且邻接点未访问
		if (G.arc[i][j] == 1 && !visited[j])
		{
			DFS(G, j);
		}
	}
}

void DFSTraverse(MGraph G)
{
	size_t i;
	for (i = 0; i < G.numVertexs; i++)
	{
		visited[i] = false;  //初始化把所有顶点设为可以访问
	}
	for (i = 0; i < G.numVertexs; i++)
	{
		//对未访问的顶点调用DFS方法
		if (!visited[i])
		{
			DFS(G, i);
		}
	}
}

2、广度优先遍历  

BreadthFirstSearch 

从顶点A开始,依次访问A的各个未被访问的邻接点,分别以这些邻接点出发,依次访问他们未被访问的邻接点。直到所有顶点都被访问。

借助队列用邻接矩阵实现的算法思想

(1)首先访问V0,并设置访问标志,然后将v0入队;

(2)只要对不空,重复以下操作:

队头结点出队,对对头结点所有未访问过的邻接点进行入队。

//邻接矩阵实现图的广度优先遍历

//遍历递归算法
void BFS(MGraph G, int i)
{
	Link_Queue queue;
	QueueInit(&queue);
	for (size_t i = 0; i < G.numVertexs; i++)
	{
		visited[i] = false;
	}
	for (size_t i = 0; i < G.numVertexs; i++)
	{
		if (!visited[i])
		{
			cout << G.vexs[i];
			visited[i] = true;
			QueuePush(&queue, G.vexs[i]);
			while (!QueueEmpty(&queue))
			{
				QueuePop(&queue);
				for (size_t j = 0; j < G.numVertexs; j++)
				{
					if (G.arc[i][j] == 1 && !visited[j])
					{
						cout << G.vexs[j];
						visited[j] = true;
						QueuePush(&queue, G.vexs[j]);
					}
				}
			}
		}
	}
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值