【数据结构】图

前端之家收集整理的这篇文章主要介绍了【数据结构】图前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

图的概念


1. 图
  图是一种非线性数据结构,由顶点的集合及顶点间的关系(边)组成;G=(V,E),其中顶点集合V是有穷非空集合;E={(x,y) | x,y属于V}或E={<x,y> | x,y属于V}是顶点间关系的有穷集合,也叫做边的集合;其中第一种圆括号表示x到y的一条双向通路,即:(x,y)(y,x)是一条边,这种边与顶点构成的图叫做无向图;第二种尖括号表示x到y的一条单向通路,也就是说:<x,y>是从x到y的一条边与<y,x>并不是同一个,这种边与顶点构成的图叫做有向图。
  
2. 完全图
  在无向图中,如果任意两条顶点之间有且仅有一条边,即:有n个顶点,就有n*(n-1)/2条边,则称此图为无向完全图;
  在有向图中,如果任意两条顶点之间有且仅有方向相反的边,那么称此图为有向完全图;若有n条边,则有n*(n-1)条边。
  
3. 邻接顶点
  在无向图中,如果(x,y)是图中的一条边,则称x与y互为邻接顶点,边(x,y)依附于顶点x和y;
  在有向图中,如果<x,y>是图中的一条边,则称顶点x邻接到y,y邻接自x,边(x,y)与顶点x,y相关联。
  
4. 顶点的度:顶点的度是与顶点相关的边的个数。在无向图中,我们把以顶点v为终点的边的条数称为入度,把以顶点v为起始点的边的个数称为出度,有向图顶点的度等于入度与出度之和。

5. 权:边附带的数据信息
例如:

6. 路径与路径长度
  从顶点x出发有一组边可以使其到达顶点y,则称这一组边为顶点x到顶点y的路径。
  对于不带权的图:路径长度是该路径上边的条数;对于带权的图,路径长度为路径上各个边的权值之和。
  
7. 子图:设图G={V,E}和图G1={V1,E1},若V1属于V且E1属于E,则称G1是G的子图。

8. 连通图与强连通图
  在无向图中,任意两个顶点之间都有一条路径,那么称此图为连通图;n个顶点的连通图至少有n-1条边。
  在有向图中,任意两个顶点v1,v2之间都存在一条从v1到v2,从v2到v1的路径,那么称此图为强连通图。
  
9. 生成树:一个连通图的最小连通子图叫做该图的生成树。生成树不唯一。

图的存储

邻接矩阵

  我们将用一个数组表示顶点的集合,利用矩阵表示顶点间的关系
例如:

可以看到如果是无向图,我们得到的邻接矩阵是对称的。
代码如下:

  1. #include <iostream>
  2. #include <vector>
  3. #include <assert.h>
  4. using namespace std;
  5. template <class V,class W,bool IsDirect = false>//false表示为无向图
  6. class Graph
  7. {
  8. public:
  9. Graph(const V* arr,size_t size)
  10. : _v(arr,arr + size)
  11. {
  12. _edges.resize(size);
  13. for (size_t i = 0; i < size; i++)
  14. _edges[i].resize(size);
  15. }
  16. int GetIndex(const V& v)
  17. {
  18. for (size_t i = 0; i < _v.size(); i++)
  19. {
  20. if (_v[i] == v)
  21. return i;
  22. }
  23. assert(false);
  24. return -1;
  25. }
  26. void AddEdges(const V v1,const V v2,const W& weight)//存储边
  27. {
  28. size_t index1 = GetIndex(v1);
  29. size_t index2 = GetIndex(v2);
  30. _edges[index1][index2] = weight;
  31. if (!IsDirect)//无向图需要两次,有向图只需要一次 false为无向图
  32. _edges[index2][index1] = weight;
  33. }
  34. void Print()//打印矩阵
  35. {
  36. for (size_t i = 0; i < _v.size(); i++)
  37. {
  38. printf("%c ",_v[i]);
  39. for (size_t j = 0; j < _v.size(); j++)
  40. {
  41. printf("%2d ",_edges[i][j]);
  42. }
  43. cout << endl;
  44. }
  45. }
  46. size_t Dev(const V& v)//计算顶点的度
  47. {
  48. size_t index = GetIndex(v);
  49. size_t count = 0;//false表示无向图,无向图度的计算是遍历一行,
  50. //true是有向图,有向图度的计算:出度+入度
  51. for (size_t i = 0; i < _v.size(); i++)
  52. {
  53. if (_edges[index][i] > 0)
  54. count++;
  55. if (IsDirect && _edges[i][index] > 0)
  56. count++;
  57. }
  58. return count;
  59. }
  60. private:
  61. vector<V> _v;
  62. vector<vector<W>> _edges;
  63. };

  对于带权的图,我们可以将1改成权值;如果没有某条边,可以把0改成无穷大;对角线上自己到自己的边,仍然存为0。
  利用邻接矩阵存储图结构,有可能出现顶点很多,边却很少的情况,此时就会有大量的0元素,造成空间浪费。

邻接表

  利用数组表示顶点的集合,利用好链表表示边的关系。把顶点在数组中的下标存放到链表的数据域中。
  对于无向图,每条边在邻接表中出现两次;要计算某个结点的,只需要将vi对应的链表遍历一遍即可。例如,下图中,A的度为2,D的度为1
  对于有向图,每条边在邻接表中只出现一次;与顶点vi对给你个的邻接表,其结点的个数为出度;检测其他所有顶点对应的边链表,结点中的dst为i的个数是入度。例如:A的出度为1,入度为1,所以A的度为2。
  对于有向图,我们可以选择出度表或者入度表,出度表,简单来说,就是箭头指向的顶点对应的下标存储在链表结点中;入度表,箭头的根部对应的顶点的下标。如下图所示:
例如:

下面看代码

  1. #include <vector>
  2. #include <iostream>
  3. using namespace std;
  4. template <class W>
  5. struct Node
  6. {
  7. Node(const W& weight,size_t src,size_t dst)
  8. : _weight(weight),_src(src),_dst(dst),_pNext(NULL)
  9. {}
  10. W _weight;
  11. size_t _src;
  12. size_t _dst;
  13. Node* _pNext;
  14. };
  15. template <class V,bool IsDirect = false>//无向图为false;有向图为true
  16. class Graph
  17. {
  18. typedef Node<W> Node;
  19. typedef Node* pNode;
  20. public:
  21. Graph(const V* arr,arr + size)
  22. {
  23. _linkEdges.resize(size);
  24. }
  25. int GetIndex(const V& v)
  26. {
  27. for (size_t i = 0; i < _v.size(); i++)
  28. {
  29. if (_v[i] == v)
  30. return i;
  31. }
  32. return -1;
  33. }
  34. void AddEdges(const V& v1,const V& v2,const W& weight)
  35. {
  36. size_t srcIndex = GetIndex(v1);
  37. size_t dstIndex = GetIndex(v2);
  38. _Add(srcIndex,dstIndex,weight);
  39. if (!IsDirect)//false 无向图
  40. _Add(dstIndex,srcIndex,weight);
  41. }
  42. size_t Dev(const V& v)
  43. {
  44. size_t count = 0;
  45. size_t index = GetIndex(v);
  46. pNode pCur = _linkEdges[index];
  47. while (pCur)//如果是无向图,直接计算度,如果是有向图先计算出度
  48. {
  49. count++;
  50. pCur = pCur->_pNext;
  51. }
  52. if (IsDirect)
  53. {
  54. for (size_t i = 0; i < _v.size(); i++)//计算有向图的入度
  55. {
  56. if (i != index)
  57. {
  58. pCur = _linkEdges[i];
  59. while (pCur)
  60. {
  61. if (pCur->_dst == index)
  62. count++;
  63. pCur = pCur->_pNext;
  64. }
  65. }
  66. }
  67. }
  68. return count;
  69. }
  70. private:
  71. void _Add(const size_t src,const size_t dst,const W& weight)
  72. {
  73. pNode pNew = new Node(weight,src,dst);
  74. pNode pCur = _linkEdges[src];
  75.  
  76. pNew->_pNext = _linkEdges[src];
  77. _linkEdges[src] = pNew;
  78. }
  79. private:
  80. vector<V> _v;
  81. vector<pNode> _linkEdges;
  82. };

猜你在找的数据结构相关文章