图的存储和遍历

一、图的存储

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和
边关系即可。节点保存比较简单,只需要一段连续空间即可。

1、 邻接矩阵
因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:先用一
个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系。

在这里插入图片描述
注意:

  • 无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向图的邻接矩阵则不一 定是对称的,第i行(列)元素之后就是顶点i 的出(入)度。
  • 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个 顶点不通,则使用无穷大代替。
    在这里插入图片描述
  • 用邻接矩阵存储图的有点是能够快速知道两个顶点是否连通,缺陷是如果顶点比较多,边比
    较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间,并且要求两个节点之间的路径不是很好求。

在这里插入图片描述


//顶点的值类型V和边的类型W,D为true是代表有向图,false代表无向图
	template<class V, class W, bool D = false>
	class Graph
	{
	private:
		vector<V> m_vertex;//顶点集合
		vector<vector<W>> m_line;//边集合
		unordered_map<V, int> m_pos;//顶点的值对应在顶点集合中的下标
	public:
		Graph(const V* a, int n)//构造函数
		{
			m_vertex.resize(n);
			m_line.resize(n);
			for (int i = 0; i < n; i++)
			{
				m_vertex[i] = a[i];
				m_line[i].resize(n, W());
				m_pos[a[i]] = i;
			}
		}

		//获取顶点src在矩阵中的下标
		int GetPosInMatrix(const V& src)
		{
			if (m_pos.find(src) != m_pos.end())
			{
				return m_pos[src];
			}
			throw invalid_argument("非法顶点");
		}
		//添加一条从src到det权重为weight的边
		void AddEdge(const V& src, const V& det, const W& weight)
		{
			//1.先获取两个顶点在矩阵中的下标
			int srcIndex = GetPosInMatrix(src);
			int detIndex = GetPosInMatrix(det);
			if (m_line[srcIndex][detIndex] != W())//这条边已经存在了,不用在插入
			{
				return;
			}
			//2.修改邻接矩阵中的值
			m_line[srcIndex][detIndex] = weight;
			if (D == false)//无向图要添加两条边
			{
				m_line[detIndex][srcIndex] = weight;
			}
		}

		void _DFS(int srcIndex, vector<bool>& visted)
		{
			visted[srcIndex] = true;
			cout << m_vertex[srcIndex] << "[" << srcIndex << "]";
			for (int i = 0; i < m_vertex.size(); i++)
			{
				if (m_line[srcIndex][i] != W() && visted[i] != true)
				{
					cout << " -(" << m_line[srcIndex][i] << ")- ";
					_DFS(i, visted);
				}
			}
		}
		void DFS(const V& v)//深度优先遍历
		{
			int index = GetPosInMatrix(v);
			vector<bool> flag(m_vertex.size(), false);//标记已经遍历的为true
			_DFS(index, flag);
			cout << endl;
		}

		void BFS(const V& v)//广度优先遍历
		{
			int index = GetPosInMatrix(v);
			vector<bool> flag(m_vertex.size(), false);//标记已经遍历的为true
			queue<int> q;
			q.push(index);
			flag[index] = true;
			while (!q.empty())
			{
				int levelsize = q.size();//每一层的个数
				for (int i = 0; i < levelsize; i++)
				{
					int pos = q.front();
					cout << m_vertex[pos] << "[" << pos << "]";
					for (int i = 0; i < m_vertex.size(); i++)
					{
						if (flag[i] != true && m_line[pos][i] != W())
						{
							q.push(i);
							flag[i] = true;
						}
					}
					q.pop();
				}
				cout << endl;
			}
			cout << endl;
		}
	};

	void TestGraph()
	{
		string a[8] = { "詹姆斯", "库里", "韦德", "汤普森", "杜兰特", "保罗", "科比", "乔丹" };
		Graph<string, int> g(a, 8);
		g.AddEdge("詹姆斯", "韦德", 100);
		g.AddEdge("詹姆斯", "库里", 98);
		g.AddEdge("詹姆斯", "保罗", 98);
		g.AddEdge("库里", "汤普森", 99);
		g.AddEdge("库里", "杜兰特", 90);
		g.AddEdge("杜兰特", "汤普森", 90);
		g.AddEdge("科比", "乔丹", 100);
		g.DFS("詹姆斯");
		g.DFS("保罗");
		g.DFS("乔丹");
		g.BFS("詹姆斯");
		g.BFS("保罗");
		g.BFS("乔丹");
	}

在这里插入图片描述

2.邻接表
邻接表:使用数组表示顶点的集合,使用链表表示边的关系。

在这里插入图片描述
注意:

有向图中每条边在邻接表中只出现一次,与顶点vi对应的邻接表所含结点的个数,就是该顶点的出度,也称出度表,要得到vi顶点的入度,必须检测其他所有顶点对应的边链表,看有多少边顶点的dst取值是i。

在这里插入图片描述

template<class W>//两个顶点一个权重构成一个节点
	struct ListNode
	{
		int m_det;//顶点
		W m_w;//边的权重
		ListNode* m_next;
		ListNode(int det, W w) :
			m_det(det),
			m_w(w)
		{
			m_next = nullptr;
		}
	};

	template<class V, class W, bool D = false>
	class Graph
	{
	private:
		typedef ListNode<W> Edge;
		vector<V> m_vertex;//顶点集合    
		vector<Edge*> m_list;//邻接表,每个元素由一个链表组成,
		unordered_map<V, int> m_pos;//顶点的值对应在顶点集合中的下标

		//1.顶点集合存储 顶点值 ,下表对应标号
		//2.通过顶点的值在m_pos中找到对应的标号
		//3.邻接表中存储一个链表,邻接表下标代表src的下标
		//这个链表中存储的各个节点中有一个det下标和src与det之间的边权重
		//通过src的下标和det的下标在顶点集合中找到两个顶点的值,和权重w就确定了一对关系
	public:
		Graph(const V* a, int n)//构造函数
		{
			m_vertex.resize(n);
			m_list.resize(n);
			for (int i = 0; i < n; i++)
			{
				m_list[i] = nullptr;
				m_vertex[i] = a[i];
				m_pos[a[i]] = i;
			}
		}

		//获取顶点src在矩阵中的下标
		int GetPosInMatrix(const V& src)
		{
			if (m_pos.find(src) != m_pos.end())
			{
				return m_pos[src];
			}
			throw invalid_argument("非法顶点");
		}

		//判断是否存在一个关系
		bool JudgeExitLine(const V& src, const V& det, const W& weight)
		{
			int srcIndex = GetPosInMatrix(src);
			int detIndex = GetPosInMatrix(det);
			Edge* cur = m_list[srcIndex];
			while (cur)
			{
				if (cur->m_det == detIndex && cur->m_w == weight)
				{
					return true;
				}
				cur = cur->m_next;
			}
			return false;
		}

		//给m_list[src]的添加一个关系(src,det,weight)
		void AddSrcEdge(int srcIndex, int detIndex, const W& weight)
		{
			Edge* node = new Edge(detIndex, weight);
			//采用头插
			node->m_next = m_list[srcIndex];//新节点插在头上
			m_list[srcIndex] = node;//更新头结点
		}


		//添加一条从src到det权重为weight的边
		void AddEdge(const V& src, const V& det, const W& weight)
		{
			if (JudgeExitLine(src, det, weight))//判断这个关系不存在时,在插入
			{
				return;
			}
			//1.先获取两个顶点在矩阵中的下标
			int srcIndex = GetPosInMatrix(src);
			int detIndex = GetPosInMatrix(det);

			//2.给邻接表中链表添加一个节点
			AddSrcEdge(srcIndex, detIndex, weight);
			if (D == false)//无向图要添加两条边
			{
				AddSrcEdge(detIndex, srcIndex, weight);
			}
		}

		void _DFS(int srcIndex, vector<bool>& flag)
		{
			flag[srcIndex] = true;
			cout << m_vertex[srcIndex] << "[" << srcIndex << "]";
			Edge* tmp = m_list[srcIndex];
			while (tmp)
			{
				if (flag[tmp->m_det] != true)
				{
					cout << " -(" << m_list[srcIndex]->m_w << ")- ";
					_DFS(tmp->m_det, flag);
				}
				tmp = tmp->m_next;
			}
		}
		void DFS(const V& v)//深度优先遍历
		{
			int index = GetPosInMatrix(v);
			vector<bool> flag(m_vertex.size(), false);//标记已经遍历的为true
			_DFS(index, flag);
			cout << endl;
		}

		void BFS(const V& v)//广度优先遍历
		{
			int index = GetPosInMatrix(v);
			vector<bool> flag(m_vertex.size(), false);//标记已经遍历的为true
			queue<int> q;
			q.push(index);
			flag[index] = true;
			while (!q.empty())
			{
				int levelsize = q.size();//每一层的个数
				for (int i = 0; i < levelsize; i++)
				{
					int pos = q.front();
					cout << m_vertex[pos] << "[" << pos << "]";
					Edge* node = m_list[pos];
					while (node)
					{
						if (flag[node->m_det] != true)
						{
							q.push(node->m_det);
							flag[node->m_det] = true;
						}
						node = node->m_next;
					}
					q.pop();
				}
				cout << endl;
			}
			cout << endl;
		}
	};

	void TestGraph()
	{
		string a[8] = { "詹姆斯", "库里", "韦德", "汤普森", "杜兰特", "保罗", "科比", "乔丹" };
		Graph<string, int> g(a, 8);
		g.AddEdge("詹姆斯", "韦德", 100);
		g.AddEdge("詹姆斯", "库里", 98);
		g.AddEdge("詹姆斯", "保罗", 98);
		g.AddEdge("库里", "汤普森", 99);
		g.AddEdge("库里", "杜兰特", 90);
		g.AddEdge("杜兰特", "汤普森", 90);
		g.AddEdge("科比", "乔丹", 100);
		g.DFS("詹姆斯");
		g.DFS("保罗");
		g.DFS("乔丹");
		g.BFS("詹姆斯");
		g.BFS("保罗");
		g.BFS("乔丹");
	}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值