数据结构 之 图(图的遍历、拓扑排序、Dijkstra、Prim、Kruskal)

数据结构和算法中常用到的就是图,我记性不好总是忘东西,只好把一些琐碎的知识点记下来,方便以后查看。

图(Graph)由表示数据元素的集合V和表示数据之间关系的集合E组成,记为G= <V,E>,V表示顶点,E表示边。图的分类有:有向图、带权图、稀疏图、稠密图、完全图、连通图

完全图
:任何两顶点间都有边相关联,具有最大边数,C(n,2).


简单路径:序列中顶点不重复出现的路径


一个有向图中,若存在一个顶点v0,从此顶点有路径可以到达图中其他所有顶点,则称此有向图为
有根的图,v0称作
图的根


连通分量(connected component)定义为无向图中的极大连通子图。 


强连通图:对于有向图G= <V,E>,若G中任意两个顶点v
i和v
j(v
i≠v
j),都有一条从v
i到v
j的有向路径,同时还有一条从v
j到v
i的有向路径

图的存储结构为:相邻矩阵、邻接表、十字链表

十字链表(Orthogonal List)是
有向图的另一种链式存储结构,可以看成是邻接表和逆邻接表的结合。表中对应于有向图的每一条弧有一个表目,共有5个域:头(headvex)和尾(tailvex)分别表示弧头(终点)和弧尾(始点)顶点序号;tailnextarc链接指针指向下一条顶点以tailvex为弧尾的弧;headnextarc指针指向下一条以顶点headvex为弧头的弧;此外还有一个表示弧权值等信息的info域  顶点表目由3个域组成:data域存放顶点的相关信息;firstinarc链接指针指向第一条以该顶点为终点的弧;firstoutarc链接指针指向第一条以该顶点为始点的弧。所有的顶点也可以放在顺序存储结构中
《数据结构 之 图(图的遍历、拓扑排序、Dijkstra、Prim、Kruskal)》

《数据结构 之 图(图的遍历、拓扑排序、Dijkstra、Prim、Kruskal)》
《数据结构 之 图(图的遍历、拓扑排序、Dijkstra、Prim、Kruskal)》

图的深度搜索DFS:类似于树的先根序遍历,用邻接矩阵,需要检查O(n^2)次,邻接表,则需要O(n+e)次

图的广度搜索BFS: 类似于树的层次遍历,使用队列

拓扑排序:对有向无环图(DAG)建立拓扑序列的过程叫拓扑排序。主要是解决先决条件问题,以某种线性顺序来组织多项任务。 方法:从有向图中删除一个没有前驱(入度为0)的顶点,并输出它,删除途中该顶点和所有以它为起点的弧,回到第一步继续执行。使用队列实现。

void TopsortbyQueue(Graph&G)  {

  for(inti= 0;i < G.VerticesNum();i++)       // 初始化Mark数组

  G.Mark[i]= UNVISITED;

     using std::queue;  // 使用STL中的队列

  queue<int>Q;

  for(i= 0; i< G.VerticesNum();i++)  // 入度为0的顶点入队

    if(G.Indegree[i]== 0)

  Q.push(i);


   while (!Q.empty()){                // 如果队列非空

       intv = Q.front();  // 获得队列顶部元素

       Q.pop();                       // 队列顶部元素出队

       Visit(G,v);  // 问顶点v

       G.Mark[v]= VISITED;  // 将标记位设置为VISITED

       for(Edge e = G.FirstEdge(v);G.IsEdge(e);e= G.NextEdge(e)){

           G.Indegree[G.ToVertex(e)]–;   // 与该顶点相邻的顶点入度减1

           if(G.Indegree[G.ToVertex(e)]== 0)    // 如果顶点入度减为0则入队

              Q.push(G.ToVertex(e));

       }

   }

    for (i = 0; i < G.VerticesNum();i++)             // 利用标记位可以判断图中是否有环

       if(G.Mark[i]== UNVISITED) {

        cout<<“此图有环!“;

         break;

    } 

}

单源最短路径:Dijkstra算法基本思想,每条边至少检查一遍,使用最小堆找最短路径,复杂度O(V+E)lgV,适合稀疏图

把图的顶点分成两个集合S和V-S。第一个集合S表示最短距离已经确定的顶点集,即一个顶点如果属于集合S当且仅当从源点s到该顶点的最短路径已知
其余的顶点放在另一个集合V-S中。初始时,集合S只包含源点,即S ={s}。设v是V中的某个顶点,把从源点s到顶点v且中间只经过集合S中顶点的路径称为从源点到v的特殊路径,并用数组D来记录当前所找到的从源点s到每个顶点的最短特殊路径长度
从尚未确定最短路径长度的集合V-S中取出一个最短特殊路径长度最小的顶点u,将u加入集合S,同时修改数组D中由s可达的最短路径长度

class Dist  {        //Dist类,DijkstraFloyd算法用于保存最短路径信息

public:

     intindex;       //顶点的索引值,仅Dijkstra算法用到

intlength;       //当前最短路径长度

intpre;         //路径最后经过的顶点

};

每对顶点之间的最短路径:Floyd算法用相邻矩阵adj来表示带权有向图




**初始化adj(0)为相邻矩阵adj
**在矩阵adj(0)上做n次迭代,递归地产生一个矩阵序列adj(1),…,adj(k),…,adj(n)
**其中经过第k次迭代,adj(k)[i,j]的值等于从顶点vi到顶点vj路径上所经过的顶点序号不大于k的最短路径长度

由于进行第k次迭代时已求得矩阵adj(k-1),那么从顶点vi到顶点vj中间顶点的序号不大于k的最短路径有两种情况:
        * 一种是中间不经过顶点vk,那么此时就有adj(k) [i,j] = adj(k-1)[i,j]
        * 另一种是中间经过顶点vk,此时adj(k) [i,j] < adj(k-1)[i,j],那么这条由顶点vi经过vk到顶点vj的中间顶点序号不大于k的最短路径由两段组成:                  •一段是从顶点vi到顶点vk的中间顶点序号不大于k-1的最短路径                  •另一段是从顶点vk到顶点vj的中间顶点序号不大于k-1的最短路径                  •路径长度应为这两段长度之和,用下面的公式计算:adj(k) [i,j]= adj(k-1)[i,k]+ adj(k-1)[k,j]

最小生成树:
树上所有权值总和表示代价,那么在G的所有的生成树中,代价最小的生成树
MST性质:设G= <V,E>是一个联通的带权图,其中每条边(vi,vj)上带有权W(vi,vj)。集合U是顶点集V的一个非空真子集。构建生成树时需要一条边连通顶点集合U和V-U。如果(u,v)∈E,其中u∈U,v∈V-U,且边(u,v)是符合条件的权值W(u,v)最小的,那么一定存在一棵包含边(u,v)的最小生成树。


Prim算法:通过直接比较数组获取代价较小的边,需要的复杂度为O(n^2),适合稠密图

**初始状态:U ={u0},TE= {}。其中u0是顶点集合V中的某一个顶点
**在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0),将这条边加进集合TE中,同时将此边的另一顶点v0并入U。这一步骤的作用是在边集E里找一条两个顶点分别在集合U和V-U中且权值最小的边,把这条边添加到边集TE中,并把这条边上不在U中的那个顶点加入到
**如果U = V,则算法结束;否则重复步骤2
**算法结束时,TE中包含了G中的n-1条边。经过上述步骤选取到的所有边恰好就构成了图G的一棵最小生成树

Kruskal算法:O(eloge),只与边数有关,适合于构造稀疏图


n首先将G中的n个顶点看成是独立的n个连通分量,这时的状态是有n个顶点而无边的森林,可以记为T= <V,{}>。
n
然后在E中选择代价最小的边,如果该边依附于两个不同的连通分支,那么将这条边加入到T中,否则舍去这条边而选择下一条代价最小的边。
n依此类推,直到T中所有顶点都在同一个连通分量中为止,此时就得到图G的一棵最小生成树。  

void Kruskal(Graph& G, Edge*&MST)  {  // 数组MST用于保存最小生成树的边

     ParTree<int>A(G.VerticesNum());            // 等价类

     MinHeap<Edge>H(G.EdgesNum());          // 最小堆

     MST= new Edge[G.VerticesNum()-1];       // 为数组MST申请空间

     int MSTtag = 0;                           // 最小生成树的边计数

     bool heapEmpty;

     for (int i = 0; i < G.VerticesNum(); i++)         // 将图的所有边插入最小堆H中

        for(Edge e = G. FirstEdge(i);G.IsEdge(e);e= G. NextEdge(e))

           if(G.FromVertex(e)< G.ToVertex(e))  // 对于无向图,防止重复插入边

               H.Insert(e);

     int EquNum= G.VerticesNum();            // 开始n个顶点分别作为一个等价类

     while(EquNum> 1) {           // 当等价类的个数大于1时合并等价类

       heapEmpty= H.isEmpty();

       if(!heapEmpty)

          Edgee = H.RemoveMin();       // 获得一条权最小的边

       if(heapEmpty|| e.weight== INFINITY) {

           cout<< “不存在最小生成树.”<<endl;

           delete[] MST;                    // 释放空间

           MST= NULL;                   // MST赋为空

           return;

       }

       int from = G.FromVertex(e);            //记录该条边的信息

       int to = G.ToVertex(e);

      if(A.Different(from,to)){           // 边e的两个顶点不在一个等价类

         A.Union(from,to);      // 将边e的两个顶点所在的等价类合并为一个

         AddEdgetoMST(e,MST,MSTtag++);// 将边e加到MST

         EquNum–;                       // 等价类的个数减1

       }

    }

}

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/moonboat0331/article/details/10077831
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞