首页 > 其他分享 >【高阶数据结构】秘法(二)——图(一):图的基本概念和存储结构

【高阶数据结构】秘法(二)——图(一):图的基本概念和存储结构

时间:2024-09-08 23:25:28浏览次数:11  
标签:秘法 const Graph vertexs 顶点 数据结构 高阶 AddEdge size

前言:

今天我们要讲解的是数据结构中图的部分,这部分在我们实际生活中也是经常会碰到的,同时这部分也是数据结构中比较有难度的部分,这部分内容我会把它分为多章来进行讲解,今天我们先来讲解一下图的基本概念和存储结构

目录

一、图的基本概念

1. 图的定义

2. 术语解释

3. 图的分类

二、图的表示

1.邻接矩阵

2. 邻接表

三、总结


一、图的基本概念

1. 图的定义

图是一种非线性的数据结构:G=(V,E),它由节点(也称为顶点)和连接这些节点的边组成。图可以用来表示现实世界中的各种关系,如社交网络、交通网络、电路网络等。

2. 术语解释

  • 顶点(Vertex):图中的基本元素,可以表示任何实体,如人、地点、事物等。顶点集合V={x|x属于某个数据对象集}是有穷非空集合
  • 边(Edge):连接两个顶点的线,表示顶点之间的关系。边的集合:E={(x,y)|x,y属于V}(无向图)或者E={<x,y>|x,y属于V&&Path(x,y)}是顶点间关系的有穷集合
  • 邻接(Adjacent):如果两个顶点通过一条边直接相连,则称这两个顶点是邻接的。
  • 度(Degree):对于无向图而言,一个顶点的度是指与该顶点相连的边的数量。但是对于有向图来说,一个顶点的度分为入度和出度,顶点的入度是以该顶点为终点的有向边的条数,出度则是以该顶点为起点的有向边的条数,顶点的度等于入度和出度之和
  • 路径(Path):顶点序列,其中每对连续的顶点都是邻接的。
  • 环(Cycle):路径中的第一个和最后一个顶点是同一个顶点的情况。
  • 连通图:对于任意两个顶点,都存在一条路径连接它们,即图中每一个顶点都不是单独存在的,它们都可以通过各种路径互相到达(如果有顶点单独存在,没有与其它任意顶点相连,则称这个顶点为一个孤岛)
  • 连通分量:一个图中的不连通部分。

3. 图的分类

  • 无向图(Undirected Graph):连接两个顶点的边没有方向,没有方向意味着两个顶点是互相连通的,这种常见的如朋友关系图:我是你的好友,同样你也是我的好友

  • 有向图(Directed Graph):边有方向,有方向意味着一个顶点可以到另一个顶点,但是反过来不行,这种关系常见的如网页链接图:我可以点击这个链接跳到一个图片,但是无法通过这个图片再跳回原来的界面。

  • 简单图(Simple Graph):没有重复的边和自环(顶点连接到自身的边)。

  • 多重图(Multigraph):允许有重复的边和自环。

  • 带权图(Weighted Graph):每条边上都有权重,一般是数字,可以表示距离、成本等。比如,我们在修从杭州到西安的高铁时,我们可以选择经过郑州,也可以选择经过武汉,这就产生了不同的路径,我们可以比较两个路径的开支来选择经过哪个城市,这就是权值

二、图的表示

图的表示有以下三种方法:

1.邻接矩阵

  • 邻接矩阵:使用二维数组来表示图,其中矩阵的元素表示顶点之间的连接情况,顶点之间的关系只有连通与不连通,所以我们可以用0和1来表示

注意:

1、无向图的邻接矩阵是对称的,但是有向图的邻接矩阵不一定对称

2、如果边上带权值,可以用权值来代替上面的0和1,相连通的顶点可以用权值来表示,不连通的可以用无穷来表示

3、邻接矩阵的有点是可以直观的看出两个顶点之间是否相连,但是当顶点过多、边过少的时候,就会存储大量的0,就会很不方便

代码实现:

#include<iostream>
#include<vector>
#include<string>
#include<map>
template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
public:
	typedef Graph<V, W, MAX_W, Direction> Self;
	Graph() = default;
	Graph(const V* vertexs, size_t n)
	{
		_vertexs.reserve(n);
		for (size_t i = 0; i < n; ++i)
		{
			_vertexs.push_back(vertexs[i]);
			_vIndexMap[vertexs[i]] = i;
		}
		// MAX_W 作为不存在边的标识值
		_matrix.resize(n);
		for (auto& e : _matrix)
		{
			e.resize(n, MAX_W);
		}
	}
	size_t GetVertexIndex(const V& v)
	{
		auto ret = _vIndexMap.find(v);
		if (ret != _vIndexMap.end())
		{
			return ret->second;
		}
		else
		{
			throw invalid_argument("不存在的顶点");
			return -1;
		}
	}
	void _AddEdge(size_t srci, size_t dsti, const W& w)
	{
		_matrix[srci][dsti] = w;
		if (Direction == false)
		{
			_matrix[dsti][srci] = 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 << _vertexs[i] << "-" << i << " ";
		}
		cout << endl << endl;
		cout << " ";
		for (size_t i = 0; i < _vertexs.size(); ++i)
		{
			cout << i << " ";
		}
		cout << endl;
		// 打印矩阵
		for (size_t i = 0; i < _matrix.size(); ++i)
		{
			cout << i << " ";
			for (size_t j = 0; j < _matrix[i].size(); ++j)
			{
				if (_matrix[i][j] != MAX_W)
					cout << _matrix[i][j] << " ";
				else
					cout << "#" << " ";
			}
			cout << endl;
		}
		cout << endl << endl;
		// 打印所有的边
		for (size_t i = 0; i < _matrix.size(); ++i)
		{
			for (size_t j = 0; j < _matrix[i].size(); ++j)
			{
				if (i < j && _matrix[i][j] != MAX_W)
				{
					cout << _vertexs[i] << "-" << _vertexs[j] << ":" <<
						_matrix[i][j] << endl;
				}
			}
		}
	}

private:
	map<V, size_t> _vIndexMap;
	vector<V> _vertexs; // 顶点集合
	vector<vector<W>> _matrix;  //存储边集合的矩阵
};
void TestGraph()
{
	Graph<char, int, INT_MAX, 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();
}
int main()
{
	TestGraph();
	return 0;
}

运行结果:

2. 邻接表

  • 邻接表:使用列表来表示图,每个顶点对应一个列表,列表中包含所有与该顶点相连的顶点。
  • template<class W>
    struct LinkEdge
    {
    	int _srcIndex;
    	int _dstIndex;
    	W _w;
    	LinkEdge<W>* _next;
    	LinkEdge(const W& w)
    		: _srcIndex(-1)
    		, _dstIndex(-1)
    		, _w(w)
    		, _next(nullptr)
    	{}
    };
    template<class V, class W, bool Direction = false>
    class Graph
    {
    	typedef LinkEdge<W> Edge;
    public:
    	Graph(const V* vertexs, size_t n)
    	{
    		_vertexs.reserve(n);
    		for (size_t i = 0; i < n; ++i)
    		{
    			_vertexs.push_back(vertexs[i]);
    			_vIndexMap[vertexs[i]] = i;
    		}
    		_linkTable.resize(n, nullptr);
    	}
    	size_t GetVertexIndex(const V& v)
    	{
    		auto ret = _vIndexMap.find(v);
    		if (ret != _vIndexMap.end())
    		{
    			return ret->second;
    		}
    		else
    		{
    			throw invalid_argument("不存在的顶点");
    			return -1;
    		}
    	}
    	void AddEdge(const V& src, const V& dst, const W& w)
    	{
    		size_t srcindex = GetVertexIndex(src);
    		size_t dstindex = GetVertexIndex(dst);
    		// 0 1
    		Edge* sd_edge = new Edge(w);
    		sd_edge->_srcIndex = srcindex;
    		sd_edge->_dstIndex = dstindex;
    		sd_edge->_next = _linkTable[srcindex];
    		_linkTable[srcindex] = sd_edge;
    		// 1 0
    		// 无向图
    		if (Direction == false)
    		{
    			Edge* ds_edge = new Edge(w);
    			ds_edge->_srcIndex = dstindex;
    			ds_edge->_dstIndex = srcindex;
    			ds_edge->_next = _linkTable[dstindex];
    			_linkTable[dstindex] = ds_edge;
    		}
    	}
    private:
    	map<string, int> _vIndexMap;
    	vector<V> _vertexs; // 顶点集合
    	vector<Edge*> _linkTable;   // 边的集合的临接表
    };
    void TestGraph()
    {
    	string a[] = { "张三", "李四", "王五", "赵六" };
    	Graph<string, int> g1(a, 4);
    	g1.AddEdge("张三", "李四", 100);
    	g1.AddEdge("张三", "王五", 200);
    	g1.AddEdge("王五", "赵六", 30);
    }
    

  • 边列表:使用列表来存储图中的所有边,每条边由两个顶点表示。(这个不常用,在这里不做过多解释,想要了解的可以自行搜索一下)

三、总结

这篇讲的还是图的基本内容,后面我们会讲解图的广度和深度遍历,以及与图有关的算法

感谢各位大佬观看,创作不易,还望各位大佬点赞支持!!!

标签:秘法,const,Graph,vertexs,顶点,数据结构,高阶,AddEdge,size
From: https://blog.csdn.net/2301_80220607/article/details/141872579

相关文章

  • 【数据结构】栈与队列OJ题(用队列实现栈)(用栈实现队列)
    目录1.用队列实现栈oj题对比一、初始化二、出栈 三、入栈四、取队头元素:2.用栈实现队列 一、定义二、入队列三、出队列四、队头五、判空                                      ......
  • C++笔记19•数据结构:红黑树(RBTree)•
    红黑树1.简介:    红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。当搜索二叉树退化为单支树时,搜......
  • 25版王道数据结构课后习题详细分析 第八章 8.4 选择排序
    一、单项选择题————————————————————————————————————————解析:正确答案:————————————————————————————————————————解析:正确答案:——————————————————————......
  • 树形结构-数据结构
    一、基本知识树:一对多的树形结构顶层的结点:称为根节点叶子结点(终端结点):最外围的结点,只有前驱结点,没有后继结点的结点,其结点的度是0分支结点:分支点是描述数据结构中的从根部出发(对有向图而言)有入度和出度的节点,(对无向图而言)不属于叶子节点的节点。出度不为0的结点称为分枝点......
  • 并发编程数据结构-栈
    并发编程数据结构-栈有锁栈Stack1-基础线程安全栈Stack1是一个简单的线程安全栈实现,使用了std::mutex来保证push和pop操作的原子性。主要特点包括:使用std::lock_guard确保操作期间栈的线程安全。提供了两种push操作(左值引用和右值引用),优化了性能。pop操作抛......
  • 数据结构基础讲解(三)——线性表之循环链表专项练习
    本文数据结构讲解参考书目:通过网盘分享的文件:数据结构 C语言版.pdf链接: https://pan.baidu.com/s/159y_QTbXqpMhNCNP_Fls9g?pwd=ze8e 提取码:ze8e数据结构基础讲解(二)——线性表之单链表专项练习-CSDN博客 个人主页:樱娆π-CSDN博客目录循环链表双向链表......
  • 【数据结构】单链表专题
    链表的概念及结构概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车......
  • 数据结构基础讲解(二)——线性表之单链表专项练习
    本文数据结构讲解参考书目:通过网盘分享的文件:数据结构 C语言版.pdf链接: https://pan.baidu.com/s/159y_QTbXqpMhNCNP_Fls9g?pwd=ze8e 提取码:ze8e 上一节我讲了线性表中顺序表的定义以及常用的算法,那么这节我将继续讲解顺序表中的链式结构以及常见的算法。数据......
  • 数据结构基础讲解(一)——线性表之顺序表专项练习
     本文数据结构讲解参考书目:通过网盘分享的文件:数据结构 C语言版.pdf链接:https://pan.baidu.com/s/159y_QTbXqpMhNCNP_Fls9g?pwd=ze8e提取码:ze8e目录前言一.线性表的定义二.线性表的基本操作三.线性表的顺序存储和表示四.顺序表中基本操作的实现1.顺序表......
  • 马老师浑元十三刀本质是DDD程序=算法+数据结构:浑元形意太极的本质是领域驱动设计(02)
    浑元形意太极的本质是领域驱动设计(01)在软件开发的旅程中,领域驱动设计就是我们的指路明灯。它照亮了我们前进的道路,驱散了迷茫的阴霾。有了领域驱动设计的指引,我们不再畏惧未知,不再害怕挑战。我们知道,无论前方有多么艰难的障碍,都有领域驱动设计为我们指明方向。领域驱动设计就......