图-数据结构

news2024/12/25 1:07:43

图的介绍

如果你有学过《离散数学》,那么对图的概念一定不陌生,在计算机科学中,一个图就是一些顶点的集合,这些顶点通过一系列连接(结对)。顶点用圆圈表示,边就是这些圆圈之间的连线。注意:顶点也称为节点或交点,边也称为链接。

同时边可以是双向的,也可以是单向的,不一定所有的顶点中间一定要有边,如图:

大家都或多或少听说过最短路径算法,那你知道生活中有哪些场景出现过它的应用吗?没错,就是平常使用的地图导航软件,如图:

在上图中起点、终点、每一个交叉路口都可以看作是一个顶点,而连接它们的绿色通道就是边。

另外可以从图上看出,从一个地方到另一个地方有着多种路径,影响因素有距离、红绿灯、拥堵情况、事故。对于多种因素可以使用边的权重来表示,根据情况的不同给边分配数值。

我们之前学的树和链表其实都是图的特例。

图的表示方法

邻接列表

每一个顶点都会保存着与它相邻的边,并且这个边的起点是这个顶点,即只描述指向外部的边。例如下图中,B应该保存着三条边,C保存一条边,因为B有三条指向外面,而C只有一条指向外面。

而它的邻接列表表示为:

 按照这样的存储方式,想必查找两个顶点之间关系或者权重时会有点费时。

邻接矩阵

邻接矩阵由二维数组表示,行和列都表示顶点,两个顶点所对应的矩阵元素数值表示两个顶点是否相连( 0 表示不相连,非 0 表示相连以及权重值)。

将上面的图用邻接矩阵表式则为(在这里边上无权重,所有1表示的是相连):

这邻接矩阵查找就特别快了,但是往里面添加顶点的成本很高,因为要重新按照新的行和列创建一个新的矩阵,然后将已有的数据复制到新矩阵中去。

使用哪个更好

 操作       邻接列表邻接矩阵
存储空间O(V+E)O(V^2)
添加顶点O(1)O(V^2)
添加边O(1)O(1)
检查相邻性O(1)O(1)

其中V表示图中顶点的个数,E表示边的个数。

结论:大多数情况选择邻接列表。除非图比较密集,即每个顶点都与大部分顶点相邻,并且个数不多,那么就选择邻接矩阵。

图的算法实现

结构体定义

首先要理清楚结构关系,这里的图中有三种结构,分别是边、顶点、邻接链表,以下是它们的定义。

#define MaxSize 1024

typedef char DateElem; //顶点中存储的元素
//边
typedef struct _EdgeNode
{
	int adjvex; //与之相邻的节点的位置
	int weight; //边的权重
	struct _EdgeNode* next; //指向下一条与这个顶点相邻的边
}EdgeNode;


//顶点
typedef struct _VertexNode
{
	DateElem date;				//顶点的数据
	struct _EdgeNode* first;    //指向第一条与之相邻的边
}VertexNode,AdjList;

//邻接链表 
typedef struct _AdjListGraph
{
	AdjList* adjlist; //数组,保存着所有的顶点
	int vex; //顶点数
	int edge;//边数
}AdjListGraph;

邻接链表结构体中有着一个数组,数组保存着各顶点,顶点中又保存着与之相邻的边,整体是这样的结构。

图的初始化

其中 visited 数组是为了接下来的遍历操作,防止重复访问节点,因为在初始化里面出现了,那就一起写上。

bool visited[MaxSize]; //节点是否被访问过,被访问过为true
void Init(AdjListGraph& G)
{
	G.adjlist = new AdjList[MaxSize]; //像哈希表的结构
	G.edge = 0;
	G.vex = 0;

	for (int i = 0; i < MaxSize; i++)
	{
		visited[i] = false;
	}
}

图的创建

因为创建一条边,需要知道起始的顶点和结束的顶点的位置,所以使用自己定义的 Location 函数,通过你输入的顶点数据,找到这个顶点在数组中的位置。

int Location(AdjListGraph& G, DateElem c);

void Create(AdjListGraph& G)
{
	cout << "请输入顶点和边的个数:";
	cin >> G.vex >> G.edge;

	cout << "请依次输入顶点的数据:";
	
	for (int i = 0; i < G.vex; i++)
	{
		cin >> G.adjlist[i].date;
		G.adjlist[i].first = NULL;
	}


	cout << "请依次输入边的起始和终点:";
	DateElem v1, v2;
	int i1 = 0 , i2 = 0;
	
	for (int i = 0; i < G.edge; i++)
	{
		cin >> v1 >> v2;

		i1 = Location(G, v1);
		i2 = Location(G, v2);

		if (i1 != -1 && i2 != -1)
		{
			EdgeNode* tmp = new EdgeNode;
			tmp->adjvex = i2;
			tmp->next = G.adjlist[i1].first;
			G.adjlist[i1].first = tmp;
		}
	}
}

//通过顶点保存的数据找到顶点在图中的位置
int Location(AdjListGraph& G, DateElem c)
{

	for (int i = 0; i < G.vex; i++)
	{
		if (G.adjlist[i].date == c)
		{
			return i;
		}
	}

	return -1; //没找到
}

其中插入节点的操作为下图所示:

相当于链表的前插法。

图的遍历

如下图,这也是一个图结构,不过这让我想到树结构,是因为图的遍历方式和树的遍历方式有很多相似之处。

深度遍历

也就是以一个未被访问过的顶点作为起始顶点,沿当前顶点的边去访问未被访问过的顶点。

当没有未被访问过的顶点时,就回退到上一个顶点,访问别的顶点,直到所有的顶点都被访问过。

深度遍历其实迷宫问题中就出现过了,也就是这条路能走就一直走,不撞南墙不回头。

深度遍历和树的前序遍历很类似,假如起始顶点是 A,深度遍历可以是:A D F C B E,也可以是A B E F C D,只是因为输入边时的顺序不同。

//深度遍历一个节点
void DFS(AdjListGraph& G, int v)
{

	if (visited[v] == true)
	{
		return;
	}

	cout << G.adjlist[v].date << " ";
	visited[v] = true;

	EdgeNode* temp = G.adjlist[v].first;
	int cur = 0;
	while (temp != NULL)
	{
		cur = temp->adjvex; //当前节点的位置
		if (visited[cur] == false)
		{
			DFS(G, cur);
		}
		temp = temp->next;
	}

}
//深度遍历全部节点
void DFS_All(AdjListGraph& G)
{
    for (int i = 0; i < MaxSize; i++)
    {
	    visited[i] = false;
    }
	for (int i = 0; i < G.vex; i++)
	{
		if (visited[i] == false)
		{
			DFS(G, i);
		}
	}
}

广度遍历

首先以一个未被访问过的顶点作为起始顶点,访问其相邻的所有顶点。

然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直至所有顶点都被访问过,遍历结束。

广度遍历有点像树的层序遍历,例如 A 为起始顶点,那么广度遍历的结果可能是:A D C B F E 或者是:A B C D E F 。

//广度优先遍历 需要包含头文件 <queue> 
void BFS(AdjListGraph& G, int v)
{
	queue<int> q;
	q.push(v);

	int cur = -1;
	while (!q.empty())
	{
		cur = q.front(); //取队头元素
		q.pop(); //出队

		if (visited[cur] == false)
		{
			cout << G.adjlist[cur].date << " ";
			visited[cur] = true;
		}

		
        //将与 cur 顶点相邻的顶点都入队
		EdgeNode* tmp = G.adjlist[cur].first; 
		while (tmp != NULL)
		{
			q.push(tmp->adjvex);
			tmp = tmp->next;
		}


	}
}
void BFS_All(AdjListGraph& G)
{
	for (int i = 0; i < MaxSize; i++) //初始化
	{
		visited[i] = false;
	}

	for (int i = 0; i < G.vex; i++)
	{
		if (visited[i] == false)
		{
			BFS(G, i);
		}
	}
}

 

用上图的图作为演示,得到运行结果。

其实从它们的叫法中也能看出一些来。

全部代码

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

#define MaxSize 1024
typedef char DateElem;
//边
typedef struct _EdgeNode
{
	int adjvex; //与之相邻的节点
	int weight; //边的权重
	struct _EdgeNode* next; //指向下一条与这个顶点相邻的边
}EdgeNode;


//顶点
typedef struct _VertexNode
{
	DateElem date;				//顶点的数据
	struct _EdgeNode* first;//指向第一条与之相邻的边
}VertexNode,AdjList;

//邻接链表 
typedef struct _AdjListGraph
{
	AdjList* adjlist; //数组,保存着所有的顶点
	int vex; //顶点数
	int edge;//边数
}AdjListGraph;



bool visited[MaxSize]; //节点是否被访问过,被访问过为true
//图的初始化
void Init(AdjListGraph& G)
{
	G.adjlist = new AdjList[MaxSize]; //像哈希表
	G.edge = 0;
	G.vex = 0;

	for (int i = 0; i < MaxSize; i++)
	{
		visited[i] = false;
	}
}


int Location(AdjListGraph& G, DateElem c);
//图的创建
void Create(AdjListGraph& G)
{
	cout << "请输入顶点和边的个数:"<<endl;
	cin >> G.vex >> G.edge;

	cout << "请输入顶点:" << endl;
	
	for (int i = 0; i < G.vex; i++)
	{
		cin >> G.adjlist[i].date;
		G.adjlist[i].first = NULL;
	}


	cout << "请输入边:" << endl;
	DateElem v1, v2;
	int i1 = 0 , i2 = 0;
	
	for (int i = 0; i < G.edge; i++)
	{
		cin >> v1 >> v2;

		i1 = Location(G, v1);
		i2 = Location(G, v2);

		if (i1 != -1 && i2 != -1)
		{
			EdgeNode* tmp = new EdgeNode;
			tmp->adjvex = i2;
			tmp->next = G.adjlist[i1].first;
			G.adjlist[i1].first = tmp;
		}
	}
}

//通过顶点保存的数据找到顶点在图中的位置
int Location(AdjListGraph& G, DateElem c)
{

	for (int i = 0; i < G.vex; i++)
	{
		if (G.adjlist[i].date == c)
		{
			return i;
		}
	}

	return -1; //没找到
}


//图的遍历,深度和广度

//深度遍历一个节点
void DFS(AdjListGraph& G, int v)
{

	if (visited[v] == true)
	{
		return;
	}

	cout << G.adjlist[v].date << " ";
	visited[v] = true;

	EdgeNode* temp = G.adjlist[v].first;
	int cur = 0;
	while (temp != NULL)
	{
		cur = temp->adjvex; //当前节点的位置
		if (visited[cur] == false)
		{
			DFS(G, cur);
		}
		temp = temp->next;
	}

}
//深度遍历全部节点
void DFS_All(AdjListGraph& G)
{
	for (int i = 0; i < MaxSize; i++)
	{
		visited[i] = false;
	}

	for (int i = 0; i < G.vex; i++)
	{
		if (visited[i] == false)
		{
			DFS(G, i);
		}
	}
}


//广度优先遍历 包含头文件 <queue> 
void BFS(AdjListGraph& G, int v)
{
	queue<int> q;

	q.push(v);

	int cur = -1;
	while (!q.empty())
	{
		cur = q.front(); //取队头元素
		q.pop();

		if (visited[cur] == false)
		{
			cout << G.adjlist[cur].date << " ";
			visited[cur] = true;
		}

		
		EdgeNode* tmp = G.adjlist[cur].first; 
		while (tmp != NULL)
		{
			q.push(tmp->adjvex);
			tmp = tmp->next;
		}


	}
}
void BFS_All(AdjListGraph& G)
{
	for (int i = 0; i < MaxSize; i++)
	{
		visited[i] = false;
	}

	for (int i = 0; i < G.vex; i++)
	{
		if (visited[i] == false)
		{
			BFS(G, i);
		}
	}
}

int main(void)
{

	AdjListGraph G;

	//图的初始化
	Init(G);

	//图的创建
	Create(G);

	//图的遍历:深度遍历
	cout << "深度遍历:";
	DFS_All(G);

	cout << endl;

	//图的遍历:广度遍历
	cout << "广度遍历:";
	BFS_All(G);
	return 0;
} 

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

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

相关文章

webGL开发数字孪生项目的流程

数字孪生是指使用数字模型来模拟和仿真现实世界的实体或系统。WebGL&#xff08;Web Graphics Library&#xff09;是一种用于在Web浏览器中进行高性能图形渲染的JavaScript API。将数字孪生与WebGL结合起来&#xff0c;可以实现在Web环境中呈现和交互数字模型的目标。北京木奇…

不完全伽马函数-Incomplete Gamma Function

REFERENCES Abramowitz, M. and Stegun, I. A. (Eds.). Handbook of Mathematical Functions with Formulas, Graphs, and Mathematical Tables, 9th printing. New York: Dover, p. 260, 1972. Arfken, G. “The Incomplete Gamma Function and Related Functions.” 10.5 in…

FRP 内网穿透工具部署

FRP 介绍 frp 是一个专注于内网穿透的高性能反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议&#xff0c;且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 官方网站&#xff1a;https://gofrp.org/zh-cn/ 项目地…

KylinV10 将项目上传至 Github

KylinV10 将项目上传至 Github 银河麒麟操作系统 V10 是在 Ubuntu 的基础上开发的&#xff0c;所以适用于 Ubuntu 的也适用于 KylinV10 一般上传至 GitHub&#xff0c;有两种方式&#xff0c;一种是 HTTPS&#xff0c;一种是 SSH&#xff0c;但是在 KylinV10 操作系统 HTTPS 的…

Gradio入门详细教程

常用的两款AI可视化交互应用比较&#xff1a; Gradio Gradio的优势在于易用性&#xff0c;代码结构相比Streamlit简单&#xff0c;只需简单定义输入和输出接口即可快速构建简单的交互页面&#xff0c;更轻松部署模型。适合场景相对简单&#xff0c;想要快速部署应用的开发者。便…

设计模式详解---抽象工厂模式

继续前言&#xff0c;工厂模式中抽象工厂模式的讲解&#xff1a; 1. 前面的工厂模式有啥问题&#xff1f; 前面的工厂模式有这么个问题&#xff1a;一个产品就给了一个工厂&#xff0c;这样子如果产品变多&#xff0c;系统就会很复杂&#xff1a; 2. 解决方法 我们可以按照手…

如何利用Python爬取网络上的图片

在当今数字化时代&#xff0c;网络上蕴藏着丰富的图片资源。对于开发者和研究者来说&#xff0c;从网络上获取图片数据是十分常见的需求。而Python作为一种强大的编程语言&#xff0c;提供了丰富的工具和库&#xff0c;使得爬取网络上的图片变得简单和高效。本文将介绍如何利用…

扫描电镜中的信号-噪声比(SNR)参数如何优化

在扫描电镜&#xff08;SEM&#xff09;中&#xff0c;信号-噪声比&#xff08;SNR&#xff09;的优化对于获得高质量的图像和可靠的数据分析至关重要。以下是一些优化SNR的方法&#xff1a; 选择适当的检测器&#xff1a;SEM通常配备了不同类型的检测器&#xff0c;如二次电子…

cmake 最基础示例

C 代码 文件名&#xff1a;first_cmake.cpp #include <iostream> using namespace std;int main() {cout<< "A" << endl;return 0; }CMakeLists.txt 文件 #CMakeLists.txt # 设置:版本 cmake_minimum_required(VERSION 3.20)# 定义 :项目名称 …

小红书种草和抖音传播区别是什么?

目前品牌较为关注的2大平台小红书和抖音&#xff0c;两者在种草方面存在一些明显的区别。本次就存量竞争、种草形式和种草策略这三个方面入手进行分析&#xff0c;今天和大家分享下小红书种草和抖音传播区别是什么&#xff1f; 一、存量竞争下的2大平台 2个都是属于存量竞争下的…

Point A的配置方式

Point A是5g中进行资源分配的参考点&#xff0c;所以UE驻留在小区上时&#xff0c;必须要知道Point A的位置&#xff0c;才能进一步根据参数确定属于自己的资源&#xff0c;这里就整理下协议上告知UE PointA的2种方式。 先看38.211中的描述&#xff0c;Point A是RB grids的公共…

Mysql - 常用插入数据的三种方法详解及练习

目录 &#x1f959;8.1.1 mysql中常用的三种插入数据的语句 1. insert into - 插入数据 2. replace into - 插入替换数据 3. insert ignore - 如果已存在&#xff0c;忽略当前新数据 &#x1f959;8.1.2 以上三种方法的练习及区分 &#x1f959;8.1.3 说明 &#x1f959…

C# WPF上位机开发(日志调试)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 程序开发的过程中&#xff0c;调试肯定是少不了的。比如说&#xff0c;这个时候&#xff0c;我们可以设置断点、查看变量、检查函数调用堆栈等等。…

Polkadot 品牌焕新提案:重返前卫,市场营销的创新愿景

波卡的品牌形象和营销策略也许将迎来新变化。长久以来一些社区成员批评道&#xff0c;波卡的形象过于保守、太企业化&#xff0c;缺乏 Crypto 行业应有的先锋气质。 在前阵子的 Parity “去中心化” 变革中&#xff0c;Parity 的营销团队经历了大幅的变动&#xff0c;随后建立…

ToolLLM model 以及LangChain AutoGPT Xagent在调用外部工具Tools的表现对比浅析

文章主要谈及主流ToolLLM 以及高口碑Agent 在调用Tools上的一些对比&#xff0c;框架先上&#xff0c;内容会不断丰富与更新。 ToolLLM model 也就是主打Function Call 的大模型 OPENAI GPT 宇宙第一LLM NexusRaven 开源&#xff0c;可商用&#xff0c;function call的效果对…

vue3 vite动态路由的问题

因为to.matched未配到路由导致&#xff0c; vue-router.mjs:35 [Vue Router warn]: No match found for location with path "/basedata/psiIntialCustomer/add"加下面的代码&#xff0c;是解决不了问题&#xff0c;因为它只是转向了404页面。 const routes_404 {…

Vmare安装Centos8系统

vmare虚拟机Centos8系统安装 之前虚拟机已经安装好了&#xff0c;现在开始尝试在虚拟机里面安装系统&#xff0c;这次使用Centos8进行安装。 前提条件&#xff1a; 虚拟机安装完成 Centos8系统镜像下载完成 网上资源很多&#xff0c;如果没有也可以私信我。 本篇文章全程图片资…

ChatGPT对话为什么不用WebSocket而使用EventSource?

文章目录 1. 引言2. WebSocket和EventSource简介2.1 WebSocket2.2 EventSource 3. ChatGPT对话系统的特点4. EventSource的优势4.1 简单易用4.2 容错性强4.3 兼容性良好 5. 为何选择EventSource而非WebSocket&#xff1f;5.1 单向通信模式5.2 长轮询模式5.3 简化部署和维护 6. …

后端接受List类型参数报错:Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token

今天和前端调接口时报了"Cannot deserialize instance of java.util.ArrayList out of START_OBJECT token"错误 其实我想要的是这种类型的参数 但是前端传的是这种类型 前端传过来的更像是一个对象而不是一个列表&#xff0c;我们后端不能直接接受它 报错时后端的…

CSS特效030:日蚀动画

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…