图的简单了解

基础概念: 

图:就是一种有顶点集合和顶点间的关系组成的一种数据结构;也就是图保存着两个集合,一个是顶点的集合,另一个是顶点之间关系的集合

图可以分为有向图和无向图,顾名思义,有向图是指顶点与顶点之间相连的边是有方向的;而无向图则表示顶点与顶点之间相连的边是没有方向的。如下图所示,在有向图中,连接A、B的两条边并不等价,因为指向的方向是不同的。

完全图:是指顶点集合任意两个顶点都互相指向

有n个顶点的集合,在有向图中,边的个数为n*(n-1)的图,为完全图。(每个顶点又要指向其余的n-1个顶点)

有n个顶点的集合,在无向图中,边的个数为n*(n-1)/2的图,为完全图.

 

 

在无向图中,若顶点v1到顶点v2有路径,则称顶点v1与v2是连通的。

连通图:如果图中任意一对顶点都是连通的,则称此图为连通图。

强连通图:在有向图中,若有每一对顶点vi和vj之间都存在一条vi→vj的路径,同时也存在一条vj到vi的路径,则称此图是强连通图。

A B C D之间是连通的,E F之间是连通的,但是A,E之间却不连通,因此不是连通图 

生成树:一个连通图的最小连通图称为该图的生成树。
   最小连通图:在有n个顶点的连通图中,有n-1条边
一个有3个顶点A、B、C的图中:有两条边。如图所示:
一个图的生成树可能会有多个。

图的存储结构:

上面,我们讨论过,图主要存储两个集合:顶点和顶点之间的关系

顶点之间的关系,在带权路径中,我们可以存储权值。在非带权路径中,我们可以用特定的值,表示两顶点之间是否连通。

图的存储结构有两种,一个是邻接矩阵,另一个是邻接表。

 邻接矩阵

用数组将顶点保存,用矩阵的形式表示关系 ;不带权路径:1表示顶点之间连通,0表示顶点之间不连通。

注:在无向图中,2和3连通,在矩阵中 [2,3]=1;[3][2]=1;

但是在有向图中,2→3但是3没有指向2,因此矩阵存储时[2][3]=1,[3][2]=0

 

 存储带权路径的图时,两顶点连通并且有权值时,存储权值;否则,则存储无穷大

代码实现: 

	template<class V,class W,bool Direction=false>
	class Graph{
	public:
		Graph(V* vertex,size_t size)
		{
			
			for (size_t i = 0; i < size; ++i)
			{
				_vertex.push_back(vertex[i]);
				_vertexIndex[vertex[i]] = i;
			}
			_matrix.resize(size);
			
			for (size_t  i = 0; i < size; ++i)
			{
				_matrix[i].resize(size, W());
			}
		}
		int GetIndexofVertex(const V& v)
		{
			map<V, int>::iterator Index = _vertexIndex.find(v);
			if (Index != _vertexIndex.end())
			{
				return Index->second;
			}
			return -1;
		}
		void  ConnectVertex(const V& src, const V& des, const W& w)
		{
			int srcIndex=GetIndexofVertex(src);
			if (srcIndex == -1)
			{
				printf("起始顶点不存在\n");
				return;
			}
			int desIndex = GetIndexofVertex(des);
			if (desIndex == -1)
			{
				printf("目标顶点不存在\n");
				return;
			}
			_matrix[srcIndex][desIndex] = w;
			if (Direction == false)
			{
				_matrix[desIndex][srcIndex] = w;
			}

		}


	private:
		std::vector<V> _vertex;//存放顶点
		map<V, int>     _vertexIndex;//顶点在矩阵中对应下标
		std::vector<vector<W>> _matrix;//矩阵:表示边与边之间的关系
	};

邻接表

邻接表中依旧使用数组保存顶点的集合,但是顶点之间的边关系使用链表表示

template<class W>
	struct ListNode{
		size_t _srcIndex;
		size_t _desIndex;
		W  _w;
		ListNode<W>* _next;
	};
	template<class V,class W,bool Direction=flase>
	class Graph{
		Graph(const V* vertex,size_t size)
		{
			_vertexIndex.reserve(size);
			for (int i = 0; i < vertex.size(); ++i)
			{
				_vertex.push_back(vertex[i]);
				_vertexIndex[vertex[i]] = i;
			}
			_edge.reserve(n, nullptr);
			
		}
		int GetIndexofVertex(const V& v)
		{
			iterator Index = _vertexIndex.find(v);
			if (Index != _vertexIndex.end())
			{
				return Index->second;
			}
			return -1;
		}

			void  ConnectVertex(const V& src, const V& des, const W& w)
		{
			int srcIndex = GetIndexofVertex(src);
			if (srcIndex == -1)
			{
				printf("起始顶点不存在\n");
				return;
			}
			int desIndex = GetIndexofVertex(des);
			if (desIndex == -1)
			{
				printf("目标顶点不存在\n");
				return;
			}

			//申请一个新的节点
			ListNode<W>* node = new ListNode;
			node->_srcIndex = srcIndex;
			node->_desIndex=desIndex;
			node->_w = w;
			//进行头插
			node->_next = _vertexIndex[srcIndex];
			_vertexIndex[srcIndex] = node;

			if (Direction == false)
			{
				ListNode<W>* node = new ListNode;
				node->_srcIndex = desIndex;
				node->_desIndex = srcIndex;
				node->_w = w;

				node->_next = _vertexIndex[desIndex];
				_vertexIndex[desIndex] = node;
			}
		}

	private:
		vector<V> _vertex;            //顶点集合
		map<V, int> _vertexIndex;     //顶点对应的链表下标
		vector<ListNode<W>*> _edge;   //邻接表
	};

图的遍历

深度优先遍历

图的深度优先遍历,我们可以通过树的前、中、后序遍历来理解

邻接矩阵 :

 void _DFS(int index, vector<bool>& visited)
		{
			cout << _vertex[index] << endl;
			visited[index] = true;
			for (size_t i = 0; i < _vertex.size(); ++i)
			{
				if (_matrix[index][i] !=W() && visited[i] == false)
				{
					_DFS(i, visited);
				}
			}

		}
		void DFS(const V& v)
		{
			int index = GetIndexofVertex(v);
			if (index == -1)
			{
				printf("查询的目标不存在\n");
				return;
			}
			vector<bool> visited(_vertex.size(), false);
			_DFS(index, visited);
			return;
		}

 

广度优先遍历

图的广度优先遍历,就像当于树的层序遍历

邻接表:

      //和树的层序遍历实现方式相似,使用了一个队列
    void BFS(const V& v)
		{
			int index = GetIndexofVertex(v);
			if (index == -1)
			{
				printf("查询的目标不存在\n");
				return;
			}
			vector<bool> visited(_vertex.size(), false);
			queue<int> q;
			q.push(index);
			while (!q.empty())
			{
				int front = q.front();
				q.pop();
				visited[front] = true;
				cout << _vertex[front] << endl;
				
				for (int i = 0; i < _matrix.size(); ++i)
				{
					if (_matrix[front][i] != W() && visited[i] == false)
					{
						q.push(i);
						visited[i] = true;
					}
				}
			}
			cout << endl;
		}

最小生成树

连通图由 n 个顶点组成,则其生成树必含 n 个顶点和 n-1 条边 。因此构造最小生成树的准则有三
 
1. 只能使用图中的边来构造最小生成树
2. 只能使用恰好 n-1 条边来连接图中的 n 个顶点
3. 选用的 n-1 条边不能构成回路

构造最小生成树的方法:Kruskal算法Prim算法。这两个算法都采用了逐步求解的贪心策略

Kruskal算法:(克鲁斯卡尔算法)

在n个顶点,且没有任何边图中,不断取权值最小的一条边(若有多条,则任选一条),如果连接这条边后,不构成回路(即该边的两个顶点是出于不同的连通分量),则加入最小生成树(连接边)

 

 Prim算法:(普利姆算法)

 在n个顶点,且没有任何边图中,选取一个顶点作为起点,取该顶点权值最小的一条边(若有多条,则任选一条),连接;在把改变的另一个顶点当做起点一次迭代,直至生成最小生成树。

优点:不必判断是否生成回环

 

 克鲁斯卡尔算法和普利姆算法不一定会生成最小生成树!

这主要是因为它们应用贪心算法,而贪心算法只能求解局部最优解

至于克鲁斯卡尔算法判断是否构成回环的方法:可以使用并查集结构

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值