<高阶数据结构>图

news2024/11/25 12:58:11

  • 必要概念
    • 大致用途
  • 存图
    • 邻接矩阵
    • 邻接表
  • 遍历
    • BFS(广度优先)
    • DFS(深度优先)
  • 最小生成树
    • Kruskal算法
    • Prim算法
  • 寻最短路径
    • Dijkstra算法

必要概念

图根据有无方向分为,有向图和无向图
组成:G = (V, E)

  • 顶点集合 V
  • 边的集合 E
    G(Graph),V(Vertex),E(Edge)
    图可以说是一个灰常抽象且学习比较有挑战性的一个数据结构,一个图是由顶点集合和边集合组成的

大致用途

  • 表示交通网络图,例如,顶点是城市,边是城市之间的距离
  • 表示社交关系图

存图

一般存储我们有两种方法

邻接矩阵

  • 采用vector保存顶点
  • 采用矩阵(二维数组)保存边
    在这里插入图片描述

优点:

  1. 非常适合存储稠密图
  2. O(1)判断两个顶点的连接关系,并取得权值

缺点:

  1. 相对而言不适合用来查找一个顶点连接的所有边O(N)
template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
public:
	Graph(const V* a, size_t n)
	{
		_vertexs.reserve(n);
		for (size_t i = 0; i < n; ++i)
		{
			_vertexs.push_back(a[i]);
			_indexMap[a[i]] = i;
		}

		_matrix.resize(n);
		for (size_t i = 0; i < _matrix.size(); ++i)
		{
			_matrix[i].resize(n, MAX_W);
		}
	}
	// 获取顶点映射下标
	size_t GetVertexIndex(const V& v)
	{
		auto it = _indexMap.find(v);
		if (it != _indexMap.end())
		{
			return it->second;
		}
		else
		{
			//assert(false);
			throw std::invalid_argument("顶点不存在");

			return -1;
		}
	}

	void _AddEdge(size_t srci, size_t dsti, const W& w)
	{
		// 无向图
		if (Direction == false)
		{
			_matrix[srci][dsti] = w;
			_matrix[dsti][srci] = w;
		}
		else
		{
			_matrix[srci][dsti] = w;
		}
	}

	void AddEdge(const V& src, const V& dst, const W& w)
	{
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);

			_AddEdge(srci, dsti, w);
		}
	}

	void Print()
	{
		// 打印顶点
		for (size_t i = 0; i < _vertexs.size(); ++i)
		{
			cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
		}
		cout << endl;

		cout << "  ";
		for (size_t i = 0; i < _matrix.size(); ++i)
		{
			//cout << i << " ";
			printf("%-4d", i);
		}
		cout << endl;

		// 打印矩阵
		for (size_t i = 0; i < _matrix.size(); ++i)
		{
			cout << i << " "; // 竖下标
			for (size_t j = 0; j < _matrix.size(); ++j)
			{
				//cout << _matrix[i][j] << " ";
				if (_matrix[i][j] == MAX_W)
				{
					//cout << "* ";
					printf("%-4c", '*');
				}
				else
				{
					//cout << _matrix[i][j] << " ";
					printf("%-4d", _matrix[i][j]);
				}
			}
			cout << endl;
		}
		cout << endl;
	}
	}

private:
	std::vector<V> _vertexs;		// 顶点集合
	std::map<V, int> _indexMap;		// 顶点对应的下标关系
	std::vector<std::vector<W>> _matrix; // 邻接矩阵
};

邻接表

  • 使用vector保存所有的顶点
  • 使用链表保存与每个顶点连通的顶点

优点:

  1. 适合存储稀疏图
  2. 适合查找一个顶点连接的边

缺点:

  1. 不适合去确定两个顶点是否相连及权值

namespace link_table
{
	template<class W>
	struct Edge
	{
		int _dsti; // 目标点的下标
		W _w; // 权值
		Edge* _next;

		Edge(int dsti, const W& w)
			:_dsti(dsti)
			,_w(w)
			,_next(nullptr)
		{

		}
	};

	template<class V, class W, bool Direction = false>
	class Graph
	{
		typedef Edge<W> Edge;
	public:
		// 图的创建
		// 1.IO输入 -- 不方便测试 OJ适合
		// 2.图结构关系写到文件,读取文件
		// 3.手动添加边 -- 方便测试修改
		Graph(const V* a, size_t n)
		{
			_vertexs.reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				_vertexs.push_back(a[i]);
				_indexMap[a[i]] = i;
			}

			_tables.resize(n, nullptr);
		}

		size_t GetVertexIndex(const V& v)
		{
			auto it = _indexMap.find(v);
			if (it != _indexMap.end())
			{
				return it->second;
			}
			else
			{
				//assert(false);
				throw std::invalid_argument("顶点不存在");

				return -1;
			}
		}

		void AddEdge(const V& src, const V& dst, const W& w)
		{
			{
				size_t srci = GetVertexIndex(src);
				size_t dsti = GetVertexIndex(dst);

				// 1->2
				Edge* eg = new Edge(dsti, w);
				eg->_next = _tables[srci];
				_tables[srci] = eg;
				// 如果是无向图 2->1
				if (Direction == false)
				{
					Edge* eg = new Edge(srci, w);
					eg->_next = _tables[dsti];
					_tables[dsti] = eg;
				}
			}
		}

		void Print()
		{
			// 打印顶点
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
			}
			cout << endl;

			for (size_t i = 0; i < _tables.size(); ++i)
			{
				cout << _vertexs[i] << "[" << i << "]->";
				Edge* cur = _tables[i];
				while (cur)
				{
					cout << _vertexs[cur->_dsti] << "[" << cur->_dsti << " : " << cur->_w << "]" << "->";
					cur = cur->_next;
				}
				cout << "nullptr" << endl;
			}
		}

	private:
		std::vector<V> _vertexs;		// 顶点集合
		std::map<V, int> _indexMap;		// 顶点对应的下标关系
		std::vector<Edge*> _tables; // 邻接表
	};
	        
	void TestGraph1()
	{
		Graph<char, int, true> g("0123", 4);
		g.AddEdge('0', '1', 1);
		g.AddEdge('0', '3', 4);
		g.AddEdge('1', '3', 2);
		g.AddEdge('1', '2', 9);
		g.AddEdge('2', '3', 8);
		g.AddEdge('2', '1', 5);
		g.AddEdge('2', '0', 3);
		g.AddEdge('3', '2', 6);

		g.Print();
	}
}

遍历

BFS(广度优先)

以某一顶点为起点,一层一层的向外遍历
在这里插入图片描述
我们只需借助一个队列来辅助实现,这里我们先将A入队列,在A出队列的时候,将A连通的最近一层B,C,D入队列,B出队列时将E入队列,如此运行直到队列为空时,遍历结束,出队列顺序即时我们的遍历次序
为了防止B出队列时,再次将A和C入队列,可以开一个标记容器,标记入了队列的顶点

void BFS(const V& src)
{
	size_t srci = GetVertexIndex(src);
	// 队列和标记数组
	std::queue<int> q;
	std::vector<bool> visited(_vertexs.size(), false);

	q.push(srci);
	visited[srci] = true;
	int levelSize = 1;

	while (!q.empty())
	{
		// 一层一层出
		for (size_t i = 0; i < levelSize; ++i)
		{
			int front = q.front();
			q.pop();
			cout << front << ":" << _vertexs[front] << " ";
			// 把front的邻接顶点入队列
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				if (_matrix[front][i] != MAX_W)
				{
					if (visited[i] != true)
					{
						q.push(i);
						visited[i] = true;
					}
				}
			}
			cout << endl;
		}
		levelSize = q.size();
	}

	cout << endl;
}

DFS(深度优先)

以某一顶点为起点,深度优先遍历
在这里插入图片描述


void _DFS(size_t srci, std::vector<bool>& visited)
{
	cout << srci << ":" << _vertexs[srci] << endl;
	visited[srci] = true;

	// 找一个srci相邻的没有访问过的点,去往深度遍历	
	for (size_t i = 0; i < _vertexs.size(); ++i)
	{
		if (_matrix[srci][i] != MAX_W && visited[i] == false)
		{
			_DFS(i, visited);
		}
	}
}

void DFS(const V& src)
{
	size_t srci = GetVertexIndex(src);
	std::vector<bool> visited(_vertexs.size(), false);

	_DFS(srci, visited);
}

最小生成树

  • 构成生成树这些边加起来权值是最小的
    这里采用两种算法,两种算法都采取了贪心的策略

Kruskal算法

方法
每次找权值最小边,注意这个最小边不能构成环,将所有顶点连接起来结束算法
可以借助一个并查集结构,用于判断已选的边是否构成环
这个算法是看的局部最优边,只关注局部最优
下图是算法笔记这本书里面的算法流程图
在这里插入图片描述
定义个Edge边结构,辅助算法

struct Edge
{
	size_t _srci;
	size_t _dsti;
	W _w;

	Edge(size_t srci, size_t dsti, const W& w)
		:_srci(srci)
		, _dsti(dsti)
		, _w(w)
	{

	}

	bool operator>(const Edge& e) const 
	{  
		return _w > e._w;
	}
};

W Kruskal(Self& minTree)
{
	// 初始化minTree
	size_t n = _matrix.size();
	minTree._vertexs = _vertexs;
	minTree._indexMap = _indexMap;
	minTree._matrix.resize(n);
	for (size_t i = 0; i < n; ++i)
	{
		minTree._matrix[i].resize(n, MAX_W);
	}

	std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge >> minque;
	for (size_t i = 0; i < _matrix.size(); ++i)
	{
		for (size_t j = 0; j < _matrix[i].size(); ++j)
		{
			if (j > i && _matrix[i][j] != MAX_W)
			{
				minque.push(Edge(i, j, _matrix[i][j]));
			}
		}
	}

	//选出n-1条边
	W totalw = 0;
	int size = 0;
	UnionFindSet ufs(_matrix.size());
	while (!minque.empty())
	{
		Edge eg = minque.top();
		minque.pop();

		if (!ufs.InSet(eg._srci, eg._dsti))
		{
			cout << _vertexs[eg._srci] << "->" << _vertexs[eg._dsti] << endl;
			minTree._AddEdge(eg._srci, eg._dsti, eg._w);
			ufs.Union(eg._srci, eg._dsti);
			++size;
			totalw += eg._w;
		}
	}

	if (size == n - 1)
		return totalw;
	else
		return -1;
}

Prim算法

方法
从某一顶点开始,选择该顶点连接的边中权值最小的边,再到下一顶点选连接的边中最小权值的边,同样需要记录一下已经选过了的顶点
借助一个队列实现,和广度优先遍历方法有点类似,只不过这里只需要选择最小权值边的顶点

int Prim(Self& minTree, const V& src)
		{
			size_t srci = GetVertexIndex(src);
			size_t n = _vertexs.size();
			// 初始化minTree
			minTree._vertexs = _vertexs;
			minTree._indexMap = _indexMap;
			minTree._matrix.resize(n);
			for (size_t i = 0; i < n; ++i)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}

			vector<bool> visited(n, false);
			std::priority_queue < Edge, std::vector<Edge>, std::greater<Edge >> pq;

			for (size_t i = 0; i < n; ++i)
			{
				if (_matrix[srci][i] != MAX_W)
				{
					pq.push(Edge(srci, i, _matrix[srci][i]));
				}
			}
			
			size_t size = 0;
			visited[srci] = true;
			int total = 0;
			while (!pq.empty())
			{
				Edge front = pq.top();
				pq.pop();

				if (visited[front._dsti] == true)
				{
					continue;
				}

				cout << _vertexs[front._srci] << "->" << _vertexs[front._dsti] << endl;
				minTree._AddEdge(front._srci, front._dsti, front._w);

				if (size == n -1)
				{
					break;
				}
				
				// 入队列
				for (size_t i = 0; i < n; ++i)
				{
					if (visited[i] != true && _matrix[front._dsti][i] != MAX_W)
					{
						pq.push(Edge(front._dsti, i, _matrix[front._dsti][i]));
					}
				}

				++size;
				total += front._w;
				visited[front._dsti] = true;
			}

			return total;
		}

寻最短路径

找某个顶点到图中另一顶点走的最短路径

Dijkstra算法

使用的也是贪心的策略

  • 将选择了的顶点和没有选择的顶点分为两个集合
  • dist记录从s顶点到Q顶点的最短路径权值和
  • pPath记录满足最短路径时每个顶点的上一个顶点下标
  • 定义一个S数组记录已经确定的最短顶点,一旦选定不可更改
    下图
  1. 起始,s到s的最短路径权值和为0,dist[0]=0,上一个顶点为s下标为0,pPath[0]=0
  2. s起点,s到y的最短路径权值和为5. dist[1]=5,pPath[1]=0,s到t的最短路径权值和为10,dist[3]=10,pPath[3]=0
  3. 选权值最小的y作为起点,s通过y到t的最短路径权值和为8. 8 < 10, 更新dist[3]=8,pPath[3]=1,s通过y到x最短路径权值和为14,dist[4]=14,pPath[4]=1, ,s通过y到z最短路径权值和为7,dist[2]=7,pPath[4]=1,
    在这里插入图片描述
void PrintShortPath(const V& src, const vector<int>& dist, const vector<int>& parentPath)
{
	size_t N = _vertexs.size();
	size_t srci = GetVertexIndex(src);
	for (size_t i = 0; i < N; ++i)
	{
		if (i == srci)
			continue;

		vector<int> path;
		int parenti = i;
		while (parenti != srci)
		{
			path.push_back(parenti);
			parenti = parentPath[parenti];
		}
		path.push_back(srci);
		reverse(path.begin(), path.end());
		for (auto pos : path)
		{
			cout << _vertexs[pos] << "->";
		}
		cout << dist[i] << endl;
	}
}

void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{
	size_t srci = GetVertexIndex(src);
	size_t n = _vertexs.size();

	dist.resize(n, MAX_W);
	pPath.resize(n, -1);

	dist[srci] = W();
	pPath[srci] = 0;

	vector<bool> s(n, false);

	for (size_t i = 0; i < n; ++i)
	{
		W min = MAX_W;
		size_t u = srci;
		for (size_t j = 0; j < n; ++j)
		{
			if (s[j] != true && dist[j] < min)
			{
				min = dist[j];
				u = j;
			}
		}

		// 松弛算法
		for (size_t k = 0; k < n; ++k)
		{
			if (s[u] == false && _matrix[u][k] != MAX_W
				&& dist[u] + _matrix[u][k] < dist[k])
			{
				dist[k] = dist[u] + _matrix[u][k];
				pPath[k] = u;
			}
		}
		s[u] = true;

	}
}

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

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

相关文章

PHP环境配置

1.服务器 简单理解&#xff1a;服务器也是一台计算机&#xff0c;只是比平时用到的计算机在性能上更强大&#xff0c;开发中通常都需要将开发好的项目部署到服务器进行访问&#xff0c;例如&#xff1a;我们可以访问百度、淘宝、京东等&#xff0c;都是因为有服务器的存在&…

pip install bz2 和readline失败

python3.7.5 在跑模型时报错找不到bz2,使用pip install bz2 安装失败 bz2和readline应该是python自带的包 解决方案&#xff1a;重新编译安装python3.7.5,参考&#xff1a; https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/70RC1alpha002/softwareinstall/…

E8267D 是德科技矢量信号发生器

描述 最先进的微波信号发生器 安捷伦E8267D PSG矢量信号发生器是业界首款集成式微波矢量信号发生器&#xff0c;I/Q调制最高可达44 GHz&#xff0c;典型输出功率为23 dBm&#xff0c;最高可达20 GHz&#xff0c;对于10 GHz信号&#xff0c;10 kHz偏移时的相位噪声为-120 dBc/…

C++ 深拷贝,浅拷贝

浅拷贝&#xff08;系统默认&#xff09;&#xff1a;单纯的值传递&#xff1b;即两个类对象&#xff0c;完全一样&#xff0c;值&#xff0c;堆空间等。所以如果释放两者之中的一个堆空间&#xff0c;那么另一个的堆空间也被释放。因为他们的堆空间是同一空间。 深拷贝&#x…

关于disriminative 和 generative这两种模型

但是&#xff0c;其实&#xff0c;根据李宏毅老师讲到的&#xff0c;generative model是做了一些假设的&#xff0c;比如&#xff0c;如果使用Naive Bayes的话&#xff0c;不同特征x1,x2...之间相互独立的话&#xff0c;其实是很容易出现较大的偏差的&#xff0c;因为不同特征变…

vscode使用anaconda自带的python环境在终端运行时报错

目录 具体报错内容官方翻译报错讲人话解决方法 具体报错内容 CommandNotFoundError: Your shell has not been properly configured to use conda activate. If your shell is Bash or a Bourne variant, enable conda for the current user with$ echo ". E:\Anaconda/e…

Android Gradle 同步优化

作者&#xff1a;究极逮虾户 很多人听到方法论三个字&#xff0c;就觉得我要开始pua&#xff0c;说我阿里味&#xff0c;但是我觉得这个查问题的方式可能会对大家有点帮助。 很多人都会有这样的困扰&#xff0c;给你的一个工作内容是一个你完全陌生的东西&#xff0c;第一选择…

补贴纷争,台积电欧洲计划引发格芯抗议 | 百能云芯

台积电近期海外布局计划引发了一系列反响。 继美国工会悍拒台积电加派台湾人力到亚利桑那厂进行支持&#xff0c;在德国设厂多年的芯片代工大厂格芯因不满德国对台积电在此设厂提供50亿欧元的巨额补贴&#xff0c;扬言将向欧盟提出申诉。 此外&#xff0c;台积电还有计划在日本…

Harbor 私有仓库迁移

文章目录 一.私有仓库迁移的介绍1.为何要对Harbor 私有仓库的迁移2.Harbor 私有仓库的迁移特点3. Harbor 私有仓库的迁移注意要点 二.私有仓库迁移配置1.源Harbor配置&#xff08;192.168.198.11&#xff09;&#xff08;1&#xff09;接着以下操作查看容器状况及是否可以登录 …

如何选择合适的自动化测试工具?

自动化测试是高质量软件交付领域中最重要的实践之一。在今天的敏捷开发方法中&#xff0c;几乎任一软件开发过程都需要在开发阶段的某个时候进行自动化测试&#xff0c;以加速回归测试的工作。自动化测试工具可以帮助测试人员以及整个团队专注于自动化工具无法处理的各自任务&a…

【C++ 学习 ⑱】- 多态(上)

目录 一、多态的概念和虚函数 1.1 - 用基类指针指向派生类对象 1.2 - 虚函数和虚函数的重写 1.3 - 多态构成的条件 1.4 - 多态的应用场景 二、协变和如何析构派生类对象 2.1 - 协变 2.2 - 如何析构派生类对象 三、C11 的 override 和 final 关键字 一、多态的概念和虚…

微信扫码跳转微信小程序

一:首先声明为什么需要这样做 项目中需要在后台管理页面进行扫码支付,其他人弄了微信小程序支付,所以就需要挑战小程序进行支付,在跳转的时候需要参数例如订单编号等 二:跳转小程序的方法有多种 接口调用凭证 | 微信开放文档 具体可以参考微信开放文档 1.获取scheme码 按照文…

Spring security报栈溢出几种可能的情况

今天在运行spring security的时候&#xff0c;发现出现了栈溢出的情况&#xff0c;总结可能性如下&#xff1a; 1.UserDetailsService的实现类没有加上Service注入到容器中&#xff0c;导致容器循环寻找UserDetailsService的实现类&#xff0c;最终发生栈溢出的现象。 解决方法…

工业总线与工业以太网通信协议性能评估与比较

在现代工业自动化领域&#xff0c;通信协议是实现设备间高效通信的关键。工业总线和工业以太网是两种常见的工业通信协议&#xff0c;它们在性能和适用场景方面各有优势。本文将对工业总线和工业以太网的性能进行评估与比较&#xff0c;探讨其传输速率、实时性、可靠性等指标&a…

短视频矩阵源码saas开发搭建

一、 短视频矩阵系统源码开发部署步骤分享 确定开发环境&#xff1a;务必准备好项目的开发环境&#xff0c;包括操作系统、IDE、数据库和服务器等。 下载源码&#xff1a;从官方网站或者Github等平台下载短视频矩阵系统源码&#xff0c;并进行解压。 配置数据库&#xff1a;根…

数据结构之树型结构

相关概念树的表示二叉树二叉树性质二叉树储存 实现一颗二叉树创建遍历&#xff08;前中后序&#xff09;获取树中节点个数获取叶子节点个数获取第k层节点个数获取二叉树高度检测值为value元素是否存在层序遍历&#xff08;需要队列来实现&#xff09;判断是否为完全二叉树&…

Day48|leetcode 198.打家劫舍、213.打家劫舍II、打家劫舍|||

leetcode 198.打家劫舍 题目链接&#xff1a;198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 视频链接&#xff1a;动态规划&#xff0c;偷不偷这个房间呢&#xff1f;| LeetCode&#xff1a;198.打家劫舍_哔哩哔哩_bilibili 题目概述 你是一个专业的小偷&#xff0c;…

Java实现根据按图搜索商品数据,按图搜索获取1688商品详情数据,1688拍立淘接口,1688API接口封装方法

要通过按图搜索1688的API获取商品详情跨境属性数据&#xff0c;您可以使用1688开放平台提供的接口来实现。以下是一种使用Java编程语言实现的示例&#xff0c;展示如何通过1688开放平台API获取商品详情属性数据接口&#xff1a; 首先&#xff0c;确保您已注册成为1688开放平台…

Android工具条

在底层&#xff0c;所有通过主题得到应用条的活动都使用ActionBar类实现它的应用条。不过最新的应用条特性已经增加到AppCompat支持库中的Toolbar类。这意味着&#xff0c;如果你想在应用中使用最新的应用条特性&#xff0c;就需要使用支持库中的ToolBar类。 如何增加工具条 1…