【数据结构】图的创建和深度(DFS)广度(BFS)优先遍历

news2024/10/5 21:16:17

一、图

1.图的概念

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

2.图的种类

图分为无向图有向图

无向图:若顶点Vi到Vj之间的边没有方向,则称这条边为无向边(Edge),用序偶对(Vi,Vj)表示。

有向图:若从顶点Vi到Vj的边是有方向的,则成这条边为有向边,也称为弧(Arc)。用有序对(Vi,Vj)标示,Vi称为弧尾,Vj称为弧头。如果任意两条边之间都是有向的,则称该图为有向图。

无向图:

ed4d5b49293142e4b45ceec6ea69eaf3.png

 有向图:

2ad05866ec784190804e4c005ec24c72.png

二、图的创建(邻接矩阵)

用一维数组图中顶点的信息,用一个二维数组存储图中边的信息(各顶点之间的邻接关系)。存储顶点之间邻接关系的二维数组称为邻接矩阵。结点数为n的图G=(V,E)的邻接矩阵A是n*n的,将G的顶点编号为v1,v2,......vn。若(vi,vj)∈E,则A[i][j]=1,否则A[i][j]=0。

邻接矩阵用来存放边的,两顶点之间有边相连那么在邻接矩阵中它们对应的值为1,否则为0

就如A与B有边相连,那么它们对应的值就为一,A与A自身无边,那么就为零

1.邻接矩阵示意图

0d955af480b44fcc957bb139fa58e728.png

 通过邻接矩阵得到的结论:

  • 矩阵是对称的,可压缩存储(上(下)    三角);
  • i行或第i 列中1的个数为顶点i 的度;
  • 矩阵中1的个数的一半为图中边的数目;
  • 很容易判断顶点i 和顶点j之间是否有边相连(看矩阵中ij列值是否为1)

邻接矩阵法优点

容易实现图的操作,如:求某顶点的度、判断顶点之间是否有边(弧)、找顶点的邻接点等。

邻接矩阵法缺点

n个顶点需要n^2个单元存储边();空间效率为O(n^2)适合于存储稠密图对稀疏图而言浪费空间.

2.先创建图的结构体

typedef struct
{
	char* vexs;//存放顶点的数组
	int** arcs;//存放边的二维数组
	int vexsnum;//顶点个数
	int arcsnum;//边的数目
}graph;

3.初始化图

首先申请相关数组的节点

graph* InitGraph(int vexsnum)
{
	graph* g = new graph; //申请一个图结构体的节点
	
	g->vexs = new char[vexsnum];//申请顶点个数大小的数组
	
	g->arcs = new int* [vexsnum];//申请顶点个数大小的二维数组
	
	for (int i = 0; i < vexsnum; ++i)
	{
		g->arcs[i] = new int [vexsnum];  //给二维数组中的元素申请一个数组
		
	}
	g->vexsnum = vexsnum;
	g->arcsnum = 0;
	return g;
}

4.创建图

其中vexs表示顶点元素的数组,arcs表示存放边的二维数组

void CreateGraph(graph* g,char* vexs,int* arcs)
{
	for (int i = 0; i < g->vexsnum; ++i)
	{
		g->vexs[i] = vexs[i]; //将顶点存入存放顶点的数组中
	}

	for ( int i = 0; i < g->vexsnum; ++i)
	{
		for (int j = 0; j < g->vexsnum; ++j)
		{
			g->arcs[i][j] = *(arcs+i*g->vexsnum+j);//将传入的二维数组赋值给图结构体中的二维数组
			if (g->arcs[i][j] != 0)
			{
				++g->arcsnum;//判断边的个数
			}
		}
	}
	g->arcsnum /= 2;//得到有效边的个数
}

g->arcs[i][j] = *(arcs+i*g->vexsnum+j);该语句是将arcs一维数组赋值给g->arcs二维数组

g->arcsnum /= 2;该语句除二的原因:

判断边的个数的时候,每个顶点都判断了一遍该顶点有几条边,所以重复判断了

需要除二来得到真实边的数目

三、深度优先遍历和广度优先遍历

3.1深度优先遍历主要思路:

从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。

就如二叉树中的先序遍历一样,先访问根,左子树,再访问右子树

bf1fd80c6d8e489e877b359b5622a346.png

 就像上面二叉树的先序访问一样,不撞南墙不回头

3.2图的深度优先遍历

visited表示一个一维数组,用来标记被访问过的节点

index表示从第几个顶点开始访问

void DFS(graph*g, int* visited, int index)
{
	printf("%c ", g->vexs[index]);

	visited[index] = 1;//标记已经被访问过的顶点

	for (int i = 0; i < g->vexsnum; ++i)
	{
        //判断如果有边且该顶点没有被访问过,则可以访问
		if (g->arcs[index][i] == 1 && !visited[i])
		{
			DFS(g, visited, i);//递归访问
		}
	}
}

3.3广度优先遍历的主要思想:

从某个节点(源点)出发,一次性访问所有未被访问的邻接点,再依次从这些已访问过的邻接点出发,一层一层地访问。如下图所示,广度优先遍历是按照广度优先搜索的方式对图进行遍历的。

871f5ca560774dfa9a0e5822d8b8f367.png通过观看右边的邻接矩阵,可以发现从A点出发,实际就是第一行先走完,然后再走B对应的那一行,再走D对应的那一行,再走C对应的那一行,最后走E对应的那一行

373f43cddcf7455c8d97446de3eb10db.png

从B点出发,通过邻接矩阵可以发现,实际就是第B对应的那一行先走完,然后再走A对应的那一行,再走C对应的那一行,再走D对应的那一行,最后走E对应的那一行

3.4图的广度优先遍历

 广度优先遍历就像二叉树中的层数,一层一层的走完。

通过上面的示例可以发现,图也是一层一层的走完,但是它不是按顺序走的,而是按先访问到谁就先走谁的那一行

有了这个思想,那么很显然此时我们就可以用队列来实现了,把访问到的顶点入队,然后等访问完 了该顶点相邻的顶点,就出队访问出队的顶点和其相邻的顶点,依次重复上述步骤,直到队列为空

void BFS(graph*g,int* visited, int index)
{
	queue<int> q;
	printf("%c ", g->vexs[index]);
	visited[index] = 1;//标记
	q.push(index);//入队
	while (!q.empty())
	{
		int top = q.front();//出队
		q.pop();
		for (int i = 0; i < g->vexsnum; ++i)
		{
			if (g->arcs[top][i] == 1 && !visited[i])
			{
				printf("%c ", g->vexs[i]);
				visited[i] = 1;
				q.push(i);
			}
		}
	}
}

3.5图的实现代码

#include<iostream>
#include<queue>
using namespace std;

#define N 5  //N表示图的节点个数

typedef struct
{
	char* vexs;
	int** arcs;
	int vexsnum;
	int arcsnum;
}graph;

graph* InitGraph(int vexsnum)
{
	graph* g = new graph; //申请一个图结构体的节点
	
	g->vexs = new char[vexsnum];//申请顶点个数大小的数组
	
	g->arcs = new int* [vexsnum];//申请顶点个数大小的二维数组
	
	for (int i = 0; i < vexsnum; ++i)
	{
		g->arcs[i] = new int [vexsnum];  //给二维数组中的元素申请一个数组
		
	}
	g->vexsnum = vexsnum;
	g->arcsnum = 0;
	return g;
}

void CreateGraph(graph* g,char* vexs,int* arcs)
{
	for (int i = 0; i < g->vexsnum; ++i)
	{
		g->vexs[i] = vexs[i]; //将顶点存入存放顶点的数组中
	}

	for ( int i = 0; i < g->vexsnum; ++i)
	{
		for (int j = 0; j < g->vexsnum; ++j)
		{
		  g->arcs[i][j] = *(arcs+i*g->vexsnum+j);//将传入的二维数组赋值给图结构体中的二维数组
			if (g->arcs[i][j] != 0)
			{
				++g->arcsnum;//判断边的个数
			}
		}
	}
	g->arcsnum /= 2;//得到有效边的个数,
}

void DFS(graph*g, int* visited, int index)
{
	printf("%c ", g->vexs[index]);
	visited[index] = 1;
	for (int i = 0; i < g->vexsnum; ++i)
	{
		if (g->arcs[index][i] == 1 && !visited[i])
		{
			DFS(g, visited, i);
		}
	}
}

void BFS(graph*g,int* visited, int index)
{
	queue<int> q;
	printf("%c ", g->vexs[index]);
	visited[index] = 1;//标记
	q.push(index);//入队
	while (!q.empty())
	{
		int top = q.front();//出队
		q.pop();
		for (int i = 0; i < g->vexsnum; ++i)
		{
			if (g->arcs[top][i] == 1 && !visited[i])
			{
				printf("%c ", g->vexs[i]);
				visited[i] = 1;
				q.push(i);
			}
		}
	}
}

int main()
{
	graph* g = InitGraph(N);//初始化图
	char vexs[] = {'A','B','C','D','E'};//顶点元素
	int visited[N] = { 0 };//用来标记的数组
	int arcs[N][N] =
	{
		0,1,0,1,0,
		1,0,1,1,0,
		0,1,0,1,1,
		1,1,1,0,1,
		0,0,1,1,0
	};//邻接矩阵

	CreateGraph(g, vexs, (int*)arcs);//创建图

	DFS(g, visited, 0);//深度优先遍历
	printf("\n");

	memset(visited, 0, sizeof(int) * N);//将标记数组重新置为0

	BFS(g, visited, 1);//广度优先遍历
	return 0;
}

关于图的知识就分享到这了,谢谢支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/526703.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用于OOD预测的稳定学习

当测试数据和训练数据共享相似的分布时&#xff0c;基于深度神经网络的方法取得了惊人的性能&#xff0c;但在其他情况下可能会失败。因此&#xff0c;消除训练和测试数据之间分布变化的影响对于构建有前景的深度模型至关重要。作者考虑了一个更具挑战性的情况。通过训练样本的…

大数据Doris(二十一):Bloom Filter索引以及Doris索引总结

文章目录 Bloom Filter索引以及Doris索引总结 一、Bloom Filter索引 1、BloomFilter索引原理 2、BloomFilter索引语法 3、注意事项 二、Doris索引总结 Bloom Filter索引以及Doris索引总结 一、Bloom Filter索引 1、BloomFilter索引原理 BloomFilter是由Bloom在1970年提…

移动机器人运动规划---基于图搜索的基础知识---广度优先遍历与深度优先遍历

移动机器人运动规划---基于图搜索的基础知识---广度优先遍历与深度优先遍历 广度优先搜索&#xff08;BFS&#xff09;深度优先搜索&#xff08;DFS&#xff09;BFS vs DFS 图搜索优化的方向就是&#xff1a; 按照什么规则去访问节点&#xff0c;按照什么规则弹出节点&#xff…

快速了解 TypeScript

目录 1、简介 2、安装TypeScript 3、编译代码 4、类型注解 5、接口 6、类 7、运行TypeScript Web应用 1、简介 TypeScript是JavaScript类型的超集&#xff0c;它可以编译成纯JavaScript。 TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行&#xff0c;并且…

【哈士奇赠书活动 - 23期】-〖你好 ChatGPT〗

文章目录 ⭐️ 赠书 - 《你好 ChatGPT》⭐️ 内容简介⭐️ 作者简介⭐️ 精彩书评⭐️ 赠书活动 → 获奖名单 ⭐️ 赠书 - 《你好 ChatGPT》 ⭐️ 内容简介 人工智能&#xff08;AI&#xff09;时代已经来临&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;正在进一步…

【精选】各种节日祝福(C语言,可修改),Easyx图形库应用+源代码分享

博主&#xff1a;命运之光✨✨ 专栏&#xff1a;Easyx图形库应用&#x1f4c2; 目录 ✨一、程序展示 范例一&#xff1a;❤新年祝福❤ 范例二&#xff1a;❤母亲节祝福❤ ✨二、项目环境 简单介绍一下easyx图形库应用 Easyx图形库 ✨三、运行效果展示&#xff08;视频&am…

【C++起飞之路】初级——缺省参数、函数重载、引用

C&#xff1a;函数重载、引用 一、缺省参数&#x1f6eb;1.1 &#x1f69d;什么是缺省参数1.2 &#x1f69d;缺省参数的分类a. 全缺省参数b. 半缺省参数&#xff08;部分缺省参数&#xff09; 1.3 &#x1f69d;注意事项 二、函数重载&#x1f6eb;2.1 &#x1f69d;什么是函数…

时间复杂度:根号n一般来说大于log(n)

f ( x ) x − l o g 2 x f(x)\sqrt{x}-log_2 x f(x)x ​−log2​x 对这函数求导后&#xff0c;比较分母大小&#xff0c;可以得到结论 f ( x ) f(x) f(x)先减后增&#xff0c;分界点为 x 4 ( l n 2 ) 2 x \frac{4}{(ln2)^2} x(ln2)24​ f ( x ) f(x) f(x)的图像如下所示&a…

PPT技能之文字格式,转身的文字这样做

只要用PPT&#xff0c;一定需要设置文字格式。好的文字格式&#xff0c;给人惊艳的感觉&#xff0c;是一种愉悦的享受。 你的关注&#xff0c;是我最大的动力&#xff01;你的转发&#xff0c;我的10W&#xff01;茫茫人海有你的支持&#xff0c;给我无限动力。 1、字体。 按…

什么是Java中的阻塞队列?它有什么作用?

在Java中&#xff0c;阻塞队列是一种特殊的队列&#xff0c;它可以在队列为空或队列已满时阻塞添加或移除元素的操作。阻塞队列通常用于多线程编程中&#xff0c;可以帮助我们更加方便地进行线程通信和协作。在本文中&#xff0c;我将从面试的角度&#xff0c;详细讲解Java中的…

在线办公时代,如何选择合适的云办公软件?

文章目录 在线办公时代&#xff0c;如何选择合适的云办公软件&#xff1f;在线文档石墨文档腾讯文档飞书文档 远程控制ToDesk向日葵 会议协同腾讯会议ZOOM 总结 在线办公时代&#xff0c;如何选择合适的云办公软件&#xff1f; 随着数字经济的发展和疫情的影响&#xff0c;云办…

100天精通Python(可视化篇)——第87天:matplotlib绘制不同种类炫酷雷达图参数说明+代码实战(普通、堆叠、多个、矩阵、极坐标雷达图)

文章目录 专栏导读1. 雷达图1&#xff09;介绍2&#xff09;参数说明 2. 基本雷达图3. 堆叠雷达图4. 六边形战士5. 多个雷达图6. 雷达图矩阵7. 极坐标雷达图 专栏导读 &#x1f525;&#x1f525;本文已收录于《100天精通Python从入门到就业》&#xff1a;本专栏专门针对零基础…

做一名活动策划是什么体验

在一些不了解的人眼中&#xff0c;活动策划就是那种外表光鲜亮丽&#xff0c;气场十足&#xff0c;眼神犀利&#xff0c;跷着二郎腿&#xff0c;情绪饱满的完成一场又一场的完美的秀。 好像确实是这样&#xff0c;但是你们又知不知道这背后的一切我们活动策划到底付出了什么&a…

SpringMVC的三大功能

目录 一、初识SpringMVC 1.1 MVC的定义 1.2 MVC和SpringMVC的关系是什么? 1.3 SpringMVC的重要性 二、Spring MVC的三大功能 2.1 连接功能 2.1.1 RequestMapping 注解介绍 2.1.2 GetMapping 和 PostMapping 2.2 获取参数功能 2.2.1 传递普通参数 2.2.2 传递对象 2…

【K8s】Ingress的使用

文章目录 一、Ingress介绍1、Ingress的作用2、Ingress工作流程 二、Ingress使用1、测试数据准备2、HTTP代理3、HTTPS代理 一、Ingress介绍 1、Ingress的作用 上一章中&#xff0c;NotePort和LoadBalancer类型的Service可给集群外部机器提供访问&#xff0c;但这两种类型都有缺…

JavaScript数组

1.数组是什么 2.数组的基本使用 3.操作数组 4.数组案例 一、数组是什么&#xff1f; 1.数组(Array)是一种可以按顺序保存数据的数据类型2.为什么要使用数组&#xff1f;例如&#xff1a;如果想保存一个班所有同学的姓名怎么办&#xff1f;场景&#xff1a;如果有多个数据可以用…

vue3中ts定义对象,pinia中使用ts定义状态对象

文章目录 引入reactive中使用数组reactive中定义对象类型pinia中定义状态对象 引入 用惯了js&#xff0c;突然使用ts属实有点不习惯&#xff0c;这里介绍一下自己在vue3中使用ts初始化内容的一些小技巧 reactive中使用数组 例如下面所示的代码&#xff0c;我们就像写js代码一…

数组a与数组b作内积:即a和b所有对应位置两元素相乘 将所有的相乘结果(积)求和 numpy.inner(a,b)

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 数组a与数组b作内积&#xff1a; 即a和b所有对应位置两元素相乘 将所有的相乘结果(积)求和 numpy.inner(a,b) [太阳]选择题 请问关于以下代码的输出结果是&#xff1f; import numpy as np …

招银网络科技-2024届暑期实习-Java后端开发

目录 1.SpringBoot 中的 SpringBootApplication注解的作用是什么&#xff1f;2.SpringBoot 中你们是如何加载配置信息的&#xff1f;3.RabbitMQ 如何保证消息不丢失&#xff1f;4.如果消费者这边消费到一半宕机了怎么办&#xff1f;5.RabbitMQ 如何保证消息没有被重复消费&…

C语言函数大全-- w 开头的函数(3)

C语言函数大全 本篇介绍C语言函数大全-- w 开头的函数 1. wcsdup 1.1 函数说明 函数声明函数功能wchar_t *wcsdup(const wchar_t *str);用于复制宽字符字符串 参数&#xff1a; str &#xff1a; 待复制的宽字符串 返回值&#xff1a; 如果成功复制&#xff0c;则返回指向该…