【数据结构】图
本篇博文旨在介绍数据结构中的图;介绍了图以及图的有关概念;介绍了图的两种实现方式,并用代码进行了实现;介绍并实现了图的广度优先遍历和深度优先遍历; 图图是数据结构中的一种非线性结构,由顶点以及顶点相连的边构成 图包括有向图和无向图 无向图中,当两个顶点存在连接关系时,不区分该连接是A到B点,还是B到A点 有向图中,两个顶点存在连接关系时,要区分是A可以到B,还是B可以到A 图的基本概念完全图图中,每个顶点都与其他顶点有连线,有N*(N-1)/2条边 权重若图中的边具有数值信息,那么该数值就为该边的权重 临接顶点一条边的两个顶点互为邻接顶点 度一个顶点具有相邻边的个数 路径图中的一个顶点A,通过其他顶点(x1,x2,x3....)到达一个顶点B后,这中间经过的顶点就为A顶点B顶点之间的路径 连通图和强连通图在无向图中,当一个顶点A可以通过其他顶点到达顶点B时,A,B两个顶点就互为连通 如果一个图中的所有顶点都是连通的,那么该无向图称为连通图 在有向图中,若每一对顶点都存在路径,则称此图为强连通图 生成树一个无向连通图的生成树是它的极小连通子图,若图中有N-1个顶点,那么该图的生成树有N-1个顶点 图的两种存储方式邻接矩阵将所有顶点的信息组成顶点表, 利用二维矩阵来表示图中的连通关系 邻接矩阵实现图//方法1:邻接矩阵 template<typename V,typename W> class GraphMatrix { public: GraphMatrix(V* vertexs,size_t n,const W& invalid = W(),bool IsDirected = false) :_vertexs(vertexs,vertexs + n),_isDirected(IsDirected) { _matrix = new W*[n]; for (size_t i = 0; i < n; ++i) { _matrix[i] = new W[n]; for (size_t j = 0; j < n; ++j) { _matrix[i][j] = invalid; } } } ~GraphMatrix() {} int GetIndex(const V& v) { for (size_t i = 0; i < _vertexs.size(); ++i) { if (_vertexs[i] == v) return i; } assert(false); return -1; } void AddEdge(const V& v1,const V& v2,const W& w) { size_t src = GetIndex(v1); size_t des = GetIndex(v2); _matrix[src][des] = w; if (_isDirected == false) _matrix[des][src] = w; } protected: vector<V> _vertexs;//定点的集合 W** _matrix;//邻接矩阵边的集合 bool _isDirected;//是否是有向图 }; void TestGraphMatrix() { string city[] = { "北京","上海","广州","杭州","西安" }; GraphMatrix<string,double> gpm(city,sizeof(city) / sizeof(city[0])); gpm.AddEdge("北京",300.3); gpm.AddEdge("北京",850.5); gpm.AddEdge("北京",299); gpm.AddEdge("北京","西安",475); } 邻接表使用数组存储顶点的信息,用链表来对链接关系进行存储 邻接表实现图//方法2:用邻接表实现图 template<typename V,typename W> class GraphLink { typedef Edge<W> Node; public: GraphLink() {} GraphLink(V* vertexs,bool IsDirected = false) { _vertexs.resize(n); for (size_t i = 0; i < n; ++i) { _vertexs[i] = vertexs[i]; } _linkTables.resize(n,NULL); } ~GraphLink() {} int GetIndex(const V& v) { for (size_t i = 0; i < _vertexs.size(); ++i) { if (v == _vertexs[i]) return i; } assert(false); return -1; } void _AddEdge(const size_t& src,const size_t& des,const W&w) { //进行头插 Node* newNode = new Node(src,des,w); newNode->_next = _linkTables[src]; _linkTables[src] = newNode; } void AddEdge(const V& v1,const W& w) { size_t src = GetIndex(v1); size_t des = GetIndex(v2); _AddEdge(src,w); if (_isDirected == false) _AddEdge(des,src,w); } protected: vector<V> _vertexs;//顶点的集合,用来存储顶点的值 vector<Node*> _linkTables;//边的集合 bool _isDirected;//是否是有向图 }; 邻接矩阵和邻接表的对比1、当一个图需要经常查找两点之间的权值关系时,利用邻接矩阵进行查询,直接定位,效率高,时间复杂度为O(1) 而邻接表需要对链接表进行一一遍历,如果该点链接的顶点比较多,查找起来效率很低,时间复杂度为O(N) 2、当我们从存储空间上比较时,邻接表又是优于邻接矩阵的 邻接表只需要存储链接的顶点,而邻接矩阵却保存了所有顶点的关系(包括不连通的两个顶点的权值) 结论:一般情况下,稀疏图用邻接表,稠密图用邻接矩阵 图的深度优先和广度优先遍历深度优先遍历这里我们可以根据二叉树的前序遍历理解,当遍历一条路径时,一直访问直到其没有路径时返回,再遍历其他的路径 这里我们用递归实现 代码实现注意:此代码需要些到邻接表实现的图里 //用哈希来判断一个定点是否被访问过 //时间复杂度为O(1) void DFS_ByVector(const V& src) { size_t isrc = GetIndex(src); //定义vector,用于判断是否访问过 vector<bool> v; v.resize(_vertexs.size(),false); v[isrc] = true; _DFS_ByVector(isrc,v); cout << endl; } //判断定点是否访问过的时间复杂度为O(1) void _DFS_ByVector(const size_t src,vector<bool>&v) { Node* cur = _linkTables[src]; cout << GetV(src) << " "; while (cur) { size_t dst = cur->_des; if (v[dst] == false) { v[dst] = true; _DFS_ByVector(dst,v); } cur = cur->_next; } } 广度优先遍历这里我们也是利用二叉树来理解,这次用的是层序遍历,每一次遍历与该节点相邻的所有节点,遍历完成后,再遍历与相邻节点直接相邻的节点。 代码实现注意:此代需要写到邻接表实现的图里 void BFS_ByVector(const V&src) { //利用队列来存储 queue<size_t> q; size_t isrc = GetIndex(src); q.push(isrc); //利用vector进行判断定点是否被访问过 vector<bool> v; v.resize(_vertexs.size(),false); v[isrc] = true; while (!q.empty()) { size_t tmp = q.front(); cout << GetV(tmp) << " "; q.pop(); Node* cur = _linkTables[tmp]; while (cur) { size_t dst = cur->_des; //如果没有访问该节点,就进行访问,并标记 if (v[dst] == false) { v[dst] = true; q.push(dst); } cur = cur->_next; } } cout << endl; } 最小生成树在连通图中,有N个顶点,那么用N-1条边来所有顶点串起来,并且没有环 并且,这N-1条边都是连通图本身的边 贪心算法在解决问题的时候,总是做出在当前情况下的最优解。 注意:当前最优,也就是局部最优。并不能保证整体最优 用贪心算法实现最小生成树的两种算法 Kruskal算法每次选出权重最小的边,若该边的加入不会导致出现环,那么加入该边 下面的图截自数据结构书籍 Prim算法从一个顶点开始,选取该点邻接的权重最小的边,如果该边不会造成环的出现,那么加入该边 克鲁斯卡尔求最小生成树的算法步骤1、利用优先级队列将边按照权值的大小排列(由小到大) 2、依次访问优先级队列的以一个元素,如果最小生成树加上该边,没有构成环,则加入这条边 3、将优先级队列的首元素出队 注意点: 用并查集(前篇博文有讲,不理解的童鞋可以点击链接进行查看http://www.voidcn.com/article/p-bztyudqy-ws.html)来判断一条边的两个点是否在一个集合中 如果已经在一个集合中,那么加入该边就会造成环的出现,就不加入该边 如果不在,就加入该边 下面给出克鲁斯卡尔算法的最小生成树的代码实现注:此代码需要放入上面邻接表实现图的代码中 //求最小生成树 bool Kruskal(GraphLink<V,W>& mintree) { mintree._vertexs = _vertexs; mintree._linkTables.resize(_vertexs.size(),NULL); mintree._isDirected = _isDirected; struct Compare { bool operator()(Node* l,Node* r) { return l->_weight < r->_weight; } }; //定义优先级队列,把所有边进行存储 priority_queue<Node*,vector<Node*>,Compare> pq; for (size_t i = 0; i < _vertexs.size(); ++i) { Node* cur = _linkTables[i]; while (cur) { //只存储定顶点数比目标定点小的 if (cur->_src < cur->_des) pq.push(cur); cur = cur->_next; } } //定义并查集,判断加入的两个定点是否带环 UnionFindSet ufs(_vertexs.size()); while (!pq.empty()) { Node* top = pq.top(); pq.pop(); size_t isrc = top->_src; size_t ides = top->_des; //如果没有造成环,添加此边 if (ufs.IsInSet(isrc,ides) == false) { //添加该线 mintree._AddEdge(isrc,ides,top->_weight); mintree._AddEdge(ides,isrc,top->_weight); ufs.Union(isrc,ides); //所有元素在一个集合里,返回true if (ufs.SetSize() == 1) return true; } } return false; } 图的完整代码请看GitHub链接?https://github.com/haohaosong/DataStruct/blob/master/Graph.h (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |