图初识

一.图的基本概念

1.什么是图?

图是由顶点集合和顶点之间的关系构成的一种数据结构.
在现实世界中应用极其广泛,比如人与人之间的朋友关系,地图中各个地点之间的关系

简单的图示例:
在这里插入图片描述

2.顶点和边

图的节点称为顶点,图中节点与节点之间的关系称之为边

3.有向图和无向图

在图中如果存在节点x、y,从节点x出发可以到达节点y,但是从节点y出发不能到达节点x,称这样的图为有向图。
如果一个图中任意相连的节点,从x可以到达y,从y也可以到达x,称这样的图为无向图

4.有向完全图和无向完全图

完全图:如果从图中的任何一个节点出发都能到达其它节点,称这样的图为完全图

有向完全图:
在有向图中任意两个节点之间有且仅有方向相反的边,这样的图称之为有向完全图
无向完全图:
在无向图中任意节点间都有一条边,这样的图称之为无向完全图

5.邻接顶点

同一条边上的两个顶点称之为对方的邻接顶点

6.顶点的度

顶点的度指的是和顶点相关联的边的条数
对于有向图存在入度和出度之分,入度指的是指向是以当前顶点为终点的边的条数,出度指的是以当前顶点为源点的边的条数,有向图的度等于入度和出度之和;
无向图的度等于入度,也等于出度

7.路径

路径指的是从一个节点到另一个节点的边

8.权

和图的边相关的参数

9.路径长度

对于不带权的图,一条路径的路径长度指的是这条路径上的边的条数
对于带权的图,一条路径的路径长度指的是这条路径上的权值之和

10.简单路径和回路

如果一个路径中的所有节点都不重复,称这样的路径为简单路径
如果一条路径上的节点有重复,也就是说从某个节点出发沿着路径仍然可以到达该节点,说明这条路径有回路

11.子图

一个图的一部分

12.连通图和强连通图

连通图:
在一个图中,如果任意一对节点之间都有路径,称这个图是连通图
强连通图:
如果在一个有向图中,任意一对节点之间存在方向相反的路径,称这样的图是强连通图

13.生成树

一个图中最小连通子图称之为这个图的生成树,有n个顶点的连通图的生成树有n个顶点和n- 1条边

二.图的存储结构

1.邻接矩阵法

将节点数据存放在一个数组中,将节点之间的权值用二维矩阵表示

代码实现:
//邻接矩阵表示图
namespace LinkedMatrix
{
	/*
		V:节点类型
		W:权值类型
		IsDirect:图是否为有向图,默认为false表示无向图
	*/
	template<class V, class W, bool IsDirect = false>
	class Graph
	{
	public:
		Graph(V* nodes, int sz)
			:_nodes(nodes, nodes + sz)
		{
			_edges.resize(sz);
			for (int i = 0; i < sz; ++i)
			{
				_edges[i].resize(sz, 0);
			}
		}

		//添加一条边
		void addEdge(const V& srcNode, const V& desNode, const W& weight)
		{
			size_t srcIdx = getIndex(srcNode);
			size_t desIdx = getIndex(desNode);

			_edges[srcIdx][desIdx] = weight;
			//无向图需要添加另一条边
			if (!IsDirect)
			{
				_edges[desIdx][srcIdx] = weight;
			}
		}

		//删除一条边
		void removeEdge(const V& srcNode, const V& desNode)
		{
			size_t srcIdx = getIndex(srcNode);
			size_t desIdx = getIndex(desNode);
			
			_edges[srcIdx][desIdx] = 0;
			//无向图的话两条边都要删除
			if (!IsDirect)
			{
				_edges[desIdx][srcIdx] = 0;
			}
		}

		//获取节点的度
		size_t getDegree(const V& nodeVal)
		{
			size_t nodeIdx = getIndex(nodeVal);

			size_t degree = 0;
			size_t len = _nodes.size();
			//获取出度
			for (size_t i = 0; i < len; ++i)
			{
				if (_edges[nodeIdx][i] != 0)
				{
					++degree;
				}
			}
			//如果是有向图,获取入度
			if (IsDirect)
			{
				for (size_t i = 0; i < len; ++i)
				{
					if (_edges[i][nodeIdx] != 0)
					{
						++degree;
					}
				}
			}
			return degree;
		}

		//打印图
		void PrintGraph()
		{
			int len = _nodes.size();
			cout << "-----------图的邻接矩阵表示-----------\n\n";
			//打印第一行内容
			cout << " \t";
			for (int i = 0; i < len; ++i)
			{
				cout << _nodes[i] << "\t";
			}
			cout << endl;

			for (int i = 0; i < len; ++i)
			{
				cout << _nodes[i] << "\t";
				for (int j = 0; j < len; ++j)
				{
					cout << _edges[i][j] << "\t";
				}
				cout << endl;
			}
		}
	private:
		vector<V> _nodes;					//节点集合
		vector<vector<W>> _edges;			//边的集合,表示从当前节点出发能直接到达节点的边
		static const size_t npos = -1;		//size_t的最大值,在成员函数中有用到

		//在节点集合找节点对应下标,如果找不到返回size_t的最大值
		size_t getIndex(const V& nodeVal)
		{
			for (size_t i = 0; i < _nodes.size(); ++i)
			{
				if (_nodes[i] == nodeVal)
				{
					return i;
				}
			}
			return npos;
		}
	};
}

2.邻接表法

将数据节点存放在数组中,在另外一个数组中存放从每个节点出发的边,这些边之间用链表相连

代码实现
//邻接表表示图
namespace LinkedTable
{
	//邻接表中的一条边
	template<class W>
	struct LinkEdge
	{
		//src:源点下标, des:终点下标 weight:源点到终点的路径对应的权值
		LinkEdge(size_t src, size_t des, const W& weight)
			:_src(src)
			, _des(des)
			, _weight(weight)
			, _next(nullptr)
		{}
		size_t _src;			//边的起点下标
		size_t _des;			//边的终点下标
		W _weight;				//边的权值
		LinkEdge* _next;		//下一条边,这个属性的设置是为了保证一种链式结构的实现
	};

	/*
		V:节点数据类型
		W:权值数据类型
		IsDirect表示是否为有向图,默认false表示无向图
	*/
	template<class V, class W, bool IsDirect = false>
	class Graph
	{
		typedef LinkEdge<W> LinkEdge;	
	public:
		Graph(V* nodes, size_t sz)
			:_nodes(nodes, nodes + sz)
		{
			_edges.resize(sz, nullptr);
		}

		//添加一条边
		void AddEdge(const V& srcNode, const V& desNode, const W& weight)
		{
			size_t srcIdx = getIndex(srcNode);
			size_t desIdx = getIndex(desNode);

			AddEdgeHelper(srcIdx, desIdx, weight);
			//无向图需要再添加一条边
			if (!IsDirect)
			{
				AddEdgeHelper(desIdx, srcIdx, weight);
			}
		}

		//删除一条边
		void removeEdge(const V& srcNode, const V& desNode)
		{
			size_t srcIdx = getIndex(srcNode);
			size_t desIdx = getIndex(desNode);

			removeEdgeHelper(srcIdx, desIdx);
			if (!IsDirect)
			{
				removeEdgeHelper(desIdx, srcIdx);
			}
		}

		//获取节点的度
		size_t getDegree(const V& nodeVal)
		{
			size_t idx = getIndex(nodeVal);
			size_t degree = 0;
			LinkEdge* curNode = _edges[idx];
			//获取出度
			while (curNode)
			{
				++degree;
				curNode = curNode->_next;
			}

			//有向图需要获取入度
			if (IsDirect)
			{
				for (size_t i = 0; i < _edges.size(); ++i)
				{
					LinkEdge* node = _edges[i];
					while (node)
					{
						if (node->_des == idx)
						{
							++degree;
						}
						node = node->_next;
					}
				}
			}
			return degree;
		}

		//打印图
		void PrintGraph()
		{
			cout << "--------邻接表法创建图如下------------" << endl;
			for (size_t i = 0; i < _nodes.size(); ++i)
			{
				cout << _nodes[i] << ":  ";
				LinkEdge* curNode = _edges[i];
				while (curNode)
				{
					cout << "(" << _nodes[curNode->_src] << "--->" << _nodes[curNode->_des] << " : " << curNode->_weight << ");";
					curNode = curNode->_next;
				}
				cout << endl;
			}
		}

		//从某个节点出发进行图的广度优先遍历
		void BFS(const V& nodeVal)
		{
			queue<V> q;
			vector<bool> visited(_nodes.size(), false);	//标记数组,标记当前节点是否访问过
			q.push(nodeVal);
			BFSHelper(q, visited);
			
			//处理非连通图
			for (size_t i = 0; i < _nodes.size(); ++i)
			{
				if (!visited[i])
				{
					q.push(_nodes[i]);
					BFSHelper(q, visited);
				}
			}
		}

		//图的深度优先搜索
		void DFS()
		{
			size_t len = _nodes.size();
			for (size_t i = 0; i < len; ++i)
			{
				DFSHelper(i);
				cout << endl;
			}
		}
	private:
		vector<V> _nodes;				//节点集合
		vector<LinkEdge*> _edges;		//节点对应的边的集合,从当前下标对应节点出发的边用一个链表连接起来
		static const size_t npos = -1;	//size_t对应的最大值,用于类中的成员函数

		//找节点在节点数组中对应的下标,找不到返回npos
		size_t getIndex(const V& nodeVal)
		{
			for (size_t i = 0; i < _nodes.size(); ++i)
			{
				if (_nodes[i] == nodeVal)
				{
					return i;
				}
			}
			return npos;
		}

		//添加边的辅助函数
		void AddEdgeHelper(size_t srcIdx, size_t desIdx, const W& weight)
		{
			LinkEdge* head = _edges[srcIdx];
			LinkEdge* newEdge = new LinkEdge(srcIdx, desIdx, weight);
			//头插
			newEdge->_next = head;
			_edges[srcIdx] = newEdge;
		}

		//删除边的辅助函数
		//删除从srcIdx到desIdx的这条边
		void removeEdgeHelper(size_t srcIdx, size_t desIdx)
		{
			LinkEdge* curNode = _edges[srcIdx];
			LinkEdge* prev = nullptr;
			while (curNode)
			{
				if (curNode->_des == desIdx)
				{
					if (prev == nullptr)
					{
						_edges[srcIdx] = nullptr;
					}
					else
					{
						prev->_next = curNode->_next;
					}
					delete curNode;
					break;
				}
				prev = curNode;
				curNode = curNode->_next;
			}
		}

		//图的广度优先遍历辅助函数
		void BFSHelper(queue<V>& q, vector<bool>& visited)
		{
			while (!q.empty())
			{
				V front = q.front();
				q.pop();
				size_t index = getIndex(front);
				if (!visited[index])
				{
					cout << front << ": (";
					LinkEdge* curNode = _edges[index];
					while (curNode)
					{
						cout << _nodes[curNode->_src] << "-->" << _nodes[curNode->_des] << ";";
						curNode = curNode->_next;
					}
					cout << ")" << endl;
					visited[index] = true;
				}
			}
		}

		//深度优先搜索辅助函数
		void DFSHelper(size_t index)
		{
			cout << _nodes[index] << "(";
			LinkEdge* curNode = _edges[index];
			while (curNode)
			{
				cout << _nodes[curNode->_src] << "--->" << _nodes[curNode->_des] << ";";
				curNode = curNode->_next;
			}
			cout << ")";
		}
	};
}

三.图的两种遍历方式

这两种遍历方式的实现在邻接表表示法中都有实现

1.深度优先

一条路走到黑

2.广度优先

一次性处理一批数据

四.最小生成树

五.单源最短路径

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值