七、图

一、定义:

1、图(Graph)是由顶点的又穷有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
  注意:
      i. 线性表中把数据元素叫元素,树中将数据元素叫结点,在图中数据元素,称为顶点(Vertex);
      ii. 线性表中可以没有数据元素,称为空表。树中可以没有结点,称为空树。在图结构中,不允许没有顶点。在定义中,若V是顶点的集合,则强调了顶点集合V有穷且非空;
      iii. 线性表中,相邻的数据元素之间有线性关系,树结构中,相邻两层的结点有层次关系,在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集合可以是空的。
  2、各种图的定义:
    a. 无向边:若顶点vi到vj之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(vi,vj)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图(Undirected graphs);
     4f8bdcb707948c435db37237d45c59a2.png

b. 有向边:若从顶点vi到vj的边有方向,则称这条边为有向边,也称为弧(Arc)。用有序偶<vi,vj>来表示,vi称为弧尾(Tail),vj称为弧头(Head)。 如果图中任意两个顶点之间的边都是有向边,则称该图为有向图(Directed graphs)
    99b557eef5a141523a62ef4d5bcba149.png

连接A到D的有向边就是弧,A是弧尾,D是弧头,用<A,D>表示,不能写成<D,A>
    c. 在图中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图。如下图都不是简单图。
    484fd25bdd9414dfa5a51a73741115e9.png

d. 在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全 图。
    含有n个顶点的无向完全图有(n*(n-1))/2条边 ,下图就是一个无向完全图.
    1510140825dbf98df580a486b83311ab.png

e. 在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的完全有向图有n*(n-1)条边。
    9a6ed7cfbd84698037dd35207bbd5f9d.png

f. 其它:有很少条边或弧的图称为稀疏图,反之称为稠密图。
    有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权(weight)。这些权可以表示从一个顶点到另一个顶点的距离或者耗费。这种带权的图称之为网。如下:
    6940c454aadfeae2cf018d261cfd1dcf.png

假设有两个图G=(V,{E})和G’=(V’,{E‘}),如果V’⊆V 且E’⊆E,则称G’为G的子图(Subgraph),如下图左侧均为右侧图的子图
1f4fd47bfef2a35c47a328281500578d.png

3、图的顶点与边的关系:
    对于无向图G=(V,{E}),如果边(v,v’)∈E,则称顶点v和v’互为邻接点(Adjacent),即v和v’相邻接。边(v,v’)依附(incident)于顶点v和v’,或者说(v,v’)与顶点v和v’相关联。顶点v的度(Degree)是和v关联的边的数目,记为TD(v)。
    对于有向图G=(V,{E}),如果弧<v,v’>∈E,则称顶点v邻接到顶点v’,顶点v’邻接自顶点v,弧<v,v’>和顶点v,v’相关联。以顶点v为头的弧的数目称为v的入度(InDegree),记为ID(v);以v为尾的弧的数目称为出度(OutDegree),记为OD(v);顶点v的度为TD(v)=ID(v)+OD(v)。
    
    无向图G=(V,{E})中从顶点v到顶点v’的路径(Path)是一个顶点序列(v=vi,0 ,vi,1 ,……,vi,m =v’),其中(vi,j-1 ,vi,j)∈ E,1 <=j<=m.
    下图表示从B到D的四种路径
    59ca51053f5e4aa0652219b62ff3a918.png

在有向图中,路径也应该是有向的。如下图从B到D有两种路径,从A到B则不存在路径。
    
    8a436ab999864b465c27ae23984f0133.png

在树中根结点到任意结点的路径是唯一的,但是图中顶点与顶点之间的路径却是不唯一的。
    路径的长度是路径上的边或者弧的数目。
    第一个顶点到最后一个顶点相同的路径称为回路或环(Cycle)。序列中顶点不重复出现的路径称为简单路径。除了第一个和最后一个顶点之外,其余顶点都不重复出现的回路,称为简单回路或简单环,
    下图两个图中粗线部分都构成环,左侧是简单环,右边C重复,不是简单环。
    332c65fed6bd6824aed26132ed8de246.png

4、连通图相关术语:
    在无向图G中,r如果从顶点v到顶点v’有路径,则称v和v’是联通的。如果对于图中任意两个顶点vi,vj∈E,vi和vj都是连通的,则称G 是连通图(Connected Graph)。如下图右侧是连通图,左侧不是
    a536e1ac095d0ec63eceb6cfa66b39b4.png

无向图中的极大连通子图称为联通分量。注意:
    ▪ 要是子图;
    ▪ 子图要是联通的;
    ▪ 联通子图含有极大顶点数;
    ▪ 具有极大顶点数的联通子图包含依附于这些顶点的所有边。
    
  在有向图G中,如果对于每一对vi、vj∈V、vi != vj,从vi到vj和从vj到vi都存在路径,则称G为强联通图。 有向图中的极大强联通子图称为有向图的强连通分量。、
  
  联通图的生成树:一个连通图的生成树是一个极小的连通子图,它含有图中全部的n和顶点,但是只有足以构成一颗树的n-1个边。
  如下图,图1图4不是生成树 图2图3是生成树
  ecff7bc124984fe077189bd2bc8719b2.png

如果一个有向图恰有一个顶点的入度为0,其余顶点的出度均为1,则是一颗有向树。
  一个有向图的生成森林由若干个有向树组成,含有图中全部顶点,但只有足以构成若干棵不想交的有向树的弧。
  如下图:图1是一棵有向树,图2图3这两棵树就是图1有向树的生成森林。
  ec969a13f6ccabbf6e87e02d101d5674.png

二、图的抽象数据类型:

1、伪代码:

Class Graph()
  {
  Data;//顶点的有穷非空集合和边的集合;
  CreateGraph(G,V,VR);//按照顶点集V和边弧集VR的定义来构造图G
  DestroyGraph(G)//图G存在则销毁
  locatedVex(G,u)//若图中存在顶点u,则返回在图中的位置
  GetVex(G,v)//返回图G中顶点v的值
  PutVex(G,v,value)//将图G中顶点v赋值value
  firstAdjVex(G,v)//返回顶点v的一个邻接顶点,若顶点在G中无邻接顶点则返回空
  NextAdjVex(G,v,w)//返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最后一个邻接点则返回空
  InsertVex(G,v)//在图G中增添新顶点v
  DeleteVex(G,v)//删除图G中顶点v及向关的弧
  InsertArc(G,v,w)//在图中增加弧<v,w>,若G是无向图,还需要添加对称弧<w,v>
  DeleteArc(G,v,w)//在图G中删除弧<v,w>,若G是无向图,则还要删除对称弧<w,v>
  DFSTraverse(G)//对图G进行深度优先遍历,在遍历过程对每个顶点调用
  HFSTraverse(G)//对图G进行广度优先遍历,在遍历过程对每个顶点调用
  }

三、图的存储结构

1、邻接矩阵
    定义:图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维素组存储图中的顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
    设图G有n个顶点,则邻接矩阵是一个n*n的方针,定义为:
            1, 若(vi,vj)∈G或<vi,vj>∈G
    arc[i][i] = {
           0,反之
  
  4a6511724e7bc1f8786e956841adc307.png

上图的顶点数组为vertex[4] = {v0,v1,v2,v3},边数组arc[4][4]为上图右侧的矩阵,对于矩阵的对角线的值如arc[2][2]为0是因为不存在顶点到自身的边,arc[0][1]=1是因为v0到v1的边存在,而arc[1][3]=0是因为v1到v3的边不存在。并且有图是无向图,V1到v3的边不存在则意味着v3到v1的边也不存在。所以无向图的边数组是一个对称矩阵。
  对称矩阵:对于n阶矩阵,有aij = aji(0<=i,j<=n)。即从矩阵的左上角到右下角的主对角线为轴,右上角的元素和左下角的元素对称相等。
  优点:
  § 判断两个顶点是否有边无边非常容易
  § 要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行(或第i列)的元素之和。比如顶点v1的度就是1+0+1+0=2;
  § 求顶点vi的所有邻接点就是将矩阵第i行元素扫描一遍,arc[i][j]为1就是邻接点
  
  对于有向图:
  766fe79312d5357a6c2364b1a21bb028.png

有向图的邻接矩阵并不对称;
  有向图讲究入度和出度,顶点v1的入度为1,正好是v1列的各数之和。顶点v1的出度为2,即第v1行的各数之和。
  对于网,邻接矩阵的定义为:
  设图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
        Wij,(若(vi,vj)∈G,或<vi,vj>∈G)
  arc[i][j]{ 0, 若i=j
       ∞ ,反之
      这里wij表示(vi,vj)或<vi,vj>上的权值。∞表示一个计算机允许的,大于所有边上权值的值,也就是一个不可能的极限值。
      下图是一个有向图,右图是它的邻接矩阵
2a8406597a826a0ce5a8d9261987f13a.png

C#实现图的创建:



      namespace AdjacencMatrixForGraph
      {
       class Vertex<T>
       {
       T data;
      
       public T Data { get => data; set => data = value; }
       public Vertex(T val)
       {
       this.data = val;
       }
       }
      -------------------------------------------------------
      namespace AdjacencMatrixForGraph
      {
       class Graph
       {
       public const int MAXVEX = 100;//最大顶点数,应由用户定义
       public const int INFINITY = 65535;//表示权值的无穷
       public Vertex<int>[] vexs = new Vertex<int>[MAXVEX];//顶点表
       public int[,] arc = new int[MAXVEX, MAXVEX];//邻接矩阵
       public int numNodes, numEdges;//图中当前的顶点数和边数
      
      
       }
      }
      --------------------------------------------------------------------
      namespace AdjacencMatrixForGraph
      {
       class MGraph
       {
       public const int INFINITY = 65535;//表示权值的无穷
       public void CreateMGraph()
       {
       Graph Gp = new Graph();//new一个新图
       int i, j, k, w;
       Console.Write("请输入顶点数:");
       Gp.numNodes = Convert.ToInt32(Console.ReadLine());
       Console.Write("请输入边数:");
       Gp.numEdges = Convert.ToInt32(Console.ReadLine());
       Console.WriteLine("请输入顶点信息:");
       for (i = 0; i < Gp.numNodes; i++)
       {
       Console.WriteLine($"请输入顶点{i}的值:");
       Gp.vexs[i] = new Vertex<int>(Convert.ToInt32(Console.ReadLine()));//对图内的每个顶点赋值
       }
       for (i = 0; i < Gp.numNodes; i++)
       {
       for (j = 0; j < Gp.numNodes; j++)
       {
       if (i == j)
       Gp.arc[i, j] = 0;//顶点没有到自己的边
       else
       Gp.arc[i, j] = INFINITY;//邻接矩阵初始化
       }
       }
       for (k = 0; k < Gp.numEdges; k++)
       {
       Console.WriteLine("输入边(vi,vj)的上标i");
       i = Convert.ToInt32(Console.ReadLine());
       Console.WriteLine("输入边(vi,vj)的下标j");
       j = Convert.ToInt32(Console.ReadLine());
       Console.WriteLine("输入边(vi,vj)的权值w");
       w = Convert.ToInt32(Console.ReadLine());
       Gp.arc[i, j] = w;
       Gp.arc[j, i] = w;//因为是无向图,矩阵对称
       }
       }
       }
      }
      ---------------------------------------------------------------------------------------
      namespace AdjacencMatrixForGraph
      {
       class Program
       {
       static void Main(string[] args)
       {
       MGraph mGraph = new MGraph();
       mGraph.CreateMGraph();
      
       Console.Read();
       }
       }
      }
      

-------------------------------------------
      n个顶点和e条边的无向网图的创建,时间复杂度为O(n+n2+e),其中对邻接矩阵G.arc的初始化耗费了O(n2)的时间
  2、邻接表
    定义:邻接表(Adjacenc List):把数组和链表结合的存储方达。
    基本思想:
    ▪ 图中顶点用一个一维数组存储,,对于顶点数组中,每个元素还需要存储一个指向第一个邻接点的指针,以便于查找该顶点的边的信息;
    ▪ 图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。
19bf8316f285346575b3ab0f829c3f1c.png

如图,顶点表的各个结点由data和firstedge两个域表示,data表示数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点, 边表结点由adjvex和next两个域组成。 adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中的下一个结点的指针。
  
  对于带权值的网图,在边表结点定义中在增加一个weight的数据域,存储信息即可。
  c3ca9754f4cf5e65c062f4e78aab251d.png

3、十字链表(Orthogonal List)
    将邻接表和逆邻接表结合起来
    重新定义顶点表结点结构:
    67fc76334539b8bdc7758091c37c1176.png

firstin表示入边表头指针,指向该顶点的入边表中第一个结点。firstout表示出边表头指针,指向该顶点的出边表的第一个结点。
    重新定义边表结点结构:
    8638a6555c39d1d41d88d6db8fc6efdf.png

tailvex是指弧起点在顶点表的下标,headvex是指弧终结点在顶点表的下标,headlink是指入边表指针域,指向终点相同的下一条边,taillink是指边表指针域,指向起点相同的下一条边,如果是网,再增加weight域来存储权值。
    1cf00f6d970e47f947e93867ff8d7290.png

在有向图的应用中,十字链表是非常好的数据结构模型。
  4、邻接多重表:
    邻接表的缺点:
    
    1ed7c0384111d8416e287177ff0da2ed.png

如图,如果要删除(v0,v2)的边,则需要对邻接表中右边表的两个阴影结点进行删除,显然十分麻烦。
    类似于十字链表,重新定义边表结构:
    740ec151c857beb1b7f5cbd51195da53.png

ivex和jvex是与某条边依附的两个顶点表中下标。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。这就是邻接多重表。
    7efbb5f66542f3a9bf72eaded88fec32.png

5、边集数组:
  边集数组是由两个一维数组构成,一个存储顶点的信息;另一个存储边的信息,这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(end)和权(weight)组成。
  边数组结构:

BeginEndWeight

begin存储起点的下标,end存储终点的下标,weight是存储权值。
  
  5d3c012b47c4daf5c175872fd0d739dc.png

四、图的遍历(Traversing Graph)

定义:从图中某一顶点出发遍历图中其余顶点,且使每个顶点且被访问一次,这一过程就叫做图的遍历。
  图的遍历:优先深度遍历和优先广度遍历
  1、深度优先遍历(Depth_First_Search),也称深度优先搜索,简称DFS;
    描述:从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则选图中一个未曾被访问的顶点作为起始点,重复上述过程,直到图中所有顶点都被访问到。
    伪代码(邻接矩阵):

  bool[] visited = new bool[int.MaxValue];//声明bool类型的数组,用于表示该顶点是否被访问过了,是为true,否为false
     /// <summary>
     /// 邻接矩阵的深度优先递归算法
     /// </summary>
     /// <param name="G">传入的图</param>
     /// <param name="i">开始的顶点下标</param>
     void DFS(MGraph G, int i)
     {
     int j;
     visited[i] = true;//访问时将标记设置为true
     Console.WriteLine(G.vex[i]);//打印顶点或其他操作
    
     for (j = 0; j < G.numVertexex; j++)
     {
     if (G.arc[i, j] == 1 && !visited[j])//j顶点和i顶点向连且顶点j未被访问过
     {
     DFS(G, j);//递归
     }
     }
     }
    
     /// <summary>
     /// 邻接矩阵的深度遍历操作
     /// </summary>
     /// <param name="G">图G</param>
     void DFSTraverse(MGraph G)
     {
     int i;
     for ( i = 0; i < G.numVertexes; i++)
     {
     visited[i] = false;//初始化标记的值
     }
    
     for ( i = 0; i < G.numVertexes; i++)
     {
     if (!visited[i])
     {
     DFS(G, i);//对未访问过的顶点调用DFS,连通图只会被执行一次
     }
     }
     }

2、广度优先算法(Breath_FIRST_Search),又称为广度优先搜索,简称BFS;
    64a21ff1467eafb5ca87cd9862989750.png

  #region 广度优先算法
     bool[] visited = new bool[int.MaxValue];//声明bool类型的数组,用于表示该顶点是否被访问过了,是为true,否为false
     /// <summary>
     /// 邻接矩阵的广度优先算法
     /// </summary>
     /// <param name="G"></param>
     void BFSTraverse(MGraph G)
     {
     int i, j;
     Queue<int> Q = new Queue<int>(); ;
     for ( i = 0; i < G.numVertexes; i++)
     {
     visited[i] = false;//初始化标记数组
     }
     for ( i = 0; i < G.numVertexes; i++)
     {
     if (!visited[i])
     {
     visited[i] = true;//设置当前顶点为访问过
     Console.WriteLine(G.vexs[i]);
     Q.Enqueue(i);//入队列
     while (!(Q == null))//当前队列不为空时
     {
     i = Q.Dequeue();//出列
     for ( j = 0; j < G.numVertexes; j++)
     {
     //判断其他顶点与当前顶点是否存在边且未被访问
     if (G.arc[i, j] == 1&&!visited[j])
     {
     visited[j] = true;
     Console.WriteLine(G.vexs[j]);//打印或其他
     Q.Enqueue(j);
     }
     }
     }
     }
     }
     }
    
     #endregion

两种遍历方式在时间复杂度上时没有差别的,但深度优先适合目标明确,以找到目标为目的的情况,广度优先适合在不断扩大遍历范围时找到相对最优解的情况。

五、最小生成树

连通图的生成树:是一个极小的连通子图,含有图中的全部结点,但只有足以构成一棵树的n-1条边。
  (对于网来说)最小生成树:构造连通网的最小代价生成树称为最小生成树(Minimum Cost Spanning Tree)。
  算法:普里姆算法,科鲁斯卡算法
  1、普里姆算法(Prim)算法
  990626ed3793a098038a83c65561ee98.png

2、克鲁斯卡尔(Kruskal)算法
  eb9bc3e5e04dd13b4c68d5044b3c6ca5.png

应用:prim算法适用于稠密图
    kruskal算法适用于稀疏图
    
六、最短路径
  非网图:没有边上的权值,所谓最短路径,是指两顶点之间经过的边数最少的路径;
  网图:最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且称路径上的第一个点为源点,最后一个顶点是终点。
  1、迪杰斯特拉(Dijkstra)算法

--------------------------------
  using System;
  namespace Dijkstra算法求最小路径
  {
  class Program
  {
  static void Main(string[] args)
  {
  int[,] myGraph = new int[7,7]{
  {0,10,2,8,int.MaxValue,int.MaxValue,int.MaxValue},
  {10,0,int.MaxValue,5,7,int.MaxValue,int.MaxValue},
  {2,int.MaxValue,0,3,int.MaxValue,1,int.MaxValue},
  {8,5,3,0,int.MaxValue,4,int.MaxValue},
  {int.MaxValue,7,int.MaxValue,int.MaxValue,0,3,2},
  {int.MaxValue,int.MaxValue,1,4,3,int.MaxValue,int.MaxValue},
  {int.MaxValue,int.MaxValue,int.MaxValue,int.MaxValue,2,int.MaxValue,0}
  };
  Dijkstra(myGraph,0);
  Console.ReadLine();
  }
  /// <summary>
  /// Dijkstra算法求最短路径,权值不能为负数
  /// </summary>
  /// <param name="mgrap">用邻接矩阵表示图</param>
  /// <param name="v">起点</param>
  public static void Dijkstra(int[,] mgrap, int v)
  {
  int len = mgrap.GetLength(0);//结点的个数
  int[] dist = new int[len];//从原点v到其它的各顶点当前的最短路径长度
  int[] path = new int[len];//path[i]表示从原点到顶点i之间最短路径的前驱结点
  int[] s = new int[len];//选定的顶点集合
  int midis, i, j, u;
  u = 0;
  for (i = 0; i < len; i++)
  {
  dist[i] = mgrap[v, i];//距离初始化
  s[i] = 0; //s[]设置为空 0表示i不再s集合中
  if (mgrap[v, i] < int.MaxValue)//路径初始化
  path[i] = v;
  else
  path[i] = -1;
  }
  s[v] = 1; //原点编号v放入s中
  path[v] = 0;
  for (i = 0; i < len; i++) //循环直到所有的顶点的最小路径都求出来
  {
  midis = int.MaxValue; //midis置最小长度初值
  for (j = 0; j < len; j++)//选取不在s中具有最小距离的顶点u
  {
  if (s[j] == 0 && dist[j] < midis)
  {
  u = j;
  midis = dist[j];
  }
  }
  s[u] = 1; //顶点u加入s中
  for (j = 0; j < len; j++)//修改不在s中的顶点的距离
  {
  if (s[j] == 0)
  {
  if (mgrap[u, j] < int.MaxValue &&
  dist[u] + mgrap[u, j] < dist[j])
  {
  dist[j] = dist[u] + mgrap[u, j];
  path[i] = u;
  }
  }
  }
  }
  for (int m = 0; m < len; m++)
  {
  System.Console.WriteLine(dist[m]);
  }
  }
  }
  
  }
  § ------------------------------------------

时间复杂度:O(n^2)
  求所有顶点到所有顶点的时间复杂度O(n^3)
  2、佛洛依德(Floyd)算法

 § ------------------------------------------
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  namespace Floyd算法求最小路径
  {
  class Program
  {
  private const int MaxSize = 6;
  private const int INF = 32767; //INF表示∞
  private const int MAXV = 7; //最大顶点个数
  //结构体的成员定义里不能直接赋值,也就是等号后的应该移除,在你后面实例化整个结构体以后,
  //再对Study_Data[n].input=new double[50] 其他成员类似。顺便说下其实用class简单得多。
  struct VertexType
  {
  public int no; //顶点编号
  public int info; //顶点其他信息
  }; //顶点类型
  struct MGraph //图的定义
  {
  public int[,] edges; //邻接矩阵
  public int n, e; //顶点数,弧数
  public VertexType[] vexs; //存放顶点信息
  }; //图的邻接矩阵类型
  static void initdata()
  {
  int i, j;
  MGraph g;
  g.n = MAXV; g.e = 8;
  g.edges = new int[MAXV, MAXV];
  g.vexs = new VertexType[MAXV];
  //int [,] anArray = new int [2, 4] {{1,2,3,4},{5,6,7,8}};
  int[,] a = new int[MAXV, MAXV] {
  {0,10,2,8,INF,INF,INF},
  {10,0,INF,5,7,INF,INF},
  {2,INF,0,3,INF,1,INF},
  {8,5,3,0,INF,4,INF},
  {INF,7,INF,INF,0,3,2},
  {INF,INF,1,4,3,INF,INF},
  {INF,INF,INF,INF,2,INF,0}
  };
  for (i = 0; i < g.n; i++) //建立图的图的邻接矩阵
  for (j = 0; j < g.n; j++)
  g.edges[i, j] = a[i, j];///
  Console.WriteLine("各顶点的最短路径:");
  Floyd(g);
  
  }
  static void Floyd(MGraph g)
  {
  int[,] A = new int[MAXV, MAXV];//A用于存放当前顶点之间的最短路径长度,分量A[i][j]表示当前顶点vi到顶点vj的最短路径长度。
  int[,] path = new int[MAXV, MAXV];//从顶点vi到顶点vj的路径上所经过的顶点编号不大于k的最短路径长度。
  int i, j, k;
  for (i = 0; i < g.n; i++)
  {
  for (j = 0; j < g.n; j++)//对各个节点初始已经知道的路径和距离
  {
  A[i, j] = g.edges[i, j];
  path[i, j] = -1;
  }
  }
  for (k = 0; k < g.n; k++)
  {
  for (i = 0; i < g.n; i++)
  for (j = 0; j < g.n; j++)
  if (A[i, j] > A[i, k] + A[k, j])//从i到j的路径比从i经过k到j的路径长
  {
  A[i, j] = A[i, k] + A[k, j];//更改路径长度
  path[i, j] = k;//更改路径信息经过k
  }
  }
  Dispath(A, path, g.n); //输出最短路径
  }
  static void Dispath(int[,] A, int[,] path, int n)
  {
  int i, j;
  for (i = 0; i < n; i++)
  for (j = 0; j < n; j++)
  {
  if (A[i, j] == INF)
  {
  if (i != j)
  {
  Console.WriteLine("从{0}到{1}没有路径\n", i, j);
  }
  }
  else
  {
  Console.Write("从{0}到{1}=>路径长度:{2} 路径:", i, j, A[i, j]);
  Console.Write("{0},", i); //输出路径上的起点
  Ppath(path, i, j); //输出路径上的中间点
  Console.WriteLine("{0}", j); //输出路径上的终点
  }
  }
  }
  static void Ppath(int[,] path, int i, int j) //前向递归查找路径上的顶点
  {
  int k;
  k = path[i, j];
  if (k == -1) return; //找到了起点则返回
  Ppath(path, i, k); //找顶点i的前一个顶点k
  Console.Write("{0},", k); //输出路径上的终点
  Ppath(path, k, j); //找顶点k的前一个顶点j
  }
  static void Main(string[] args)
  {
  initdata();
  //Console.Write("{0}", MaxSize);
  //Console.Write("{0}", MAXV);
  Console.ReadKey();
  }
  }
  }
  § ------------------------------------------

时间复杂度:O(n^3)
  总结:所有顶点到所有顶点的最短路径 :floyd
    一个顶点到其它顶点的最短路径:dijkstra

七、拓扑排序

1、AOV网(Activity One Vertex Network):在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图无环为顶点表示活动的网,称为AOV网。
  2、拓扑序列:设G = (V,E)是一个具有n个顶点的有向图,V中的顶点序列v1,v2,……,vn,满足若从一个顶点vi到vj有一条路径,则在顶点序列中顶点vi必在vj之前。则称这样的序列为拓扑序列。
  3、拓扑排序:其实就是对一个有向图构造拓扑序列的过程。

 using System;
  using System.Collections.Generic;
  
  
  /// <summary>
  /// 拓扑排序
  /// </summary>
  namespace AOVNetWorkSort
  {
   public class EdgeNode //边表结点
   {
   public int adjvex; //邻接点域,储存该顶点对应的下标
   // public int weight; //储存权值,对于非网图不需要
   public EdgeNode next; //链域,指向下一个邻接点
  
   /// <summary>
   /// 空构造
   /// </summary>
   public EdgeNode()
   {
   }
  
   public EdgeNode(int adjvex, EdgeNode next)
   {
   this.adjvex = adjvex;
   this.next = next;
   }
   }
  
   public class VertexNode //顶点表结点
   {
   public int _in; //顶点入度
   public string data; //顶点域,存储顶点信息
  
   public EdgeNode firstNode; //边表头指针
  
   public VertexNode(int @in, string data, EdgeNode firstNode)
   {
   _in = @in;
   this.data = data;
   this.firstNode = firstNode;
   }
   }
  
   public class Graph
   {
   public VertexNode[] adjList; //顶点表集合
   public int numVertexes, numEdges; //顶点数和边数
   }
  
   class Program
   {
   static void Main(string[] args)
   {
   TopologgicSort(InitGraph());
   Console.ReadLine();
   }
  
   /// <summary>
   /// 拓扑排序,若GL无回路,则输出拓扑排序序列并返回true,否则返回false
   /// </summary>
   /// <param name="GL">有向图</param>
   /// <returns></returns>
   public static bool TopologgicSort(Graph GL)
   {
   EdgeNode e;
   int i, k, gettop;
   // int top = 0; //用于栈指针下标
   int count = 0; //用于统计输出顶点的个数
   Stack<int> stack = new Stack<int>(GL.adjList.Length);
   //将遍历所有顶点,将入度为0的顶点入栈
   for (i = 0; i < GL.numVertexes; i++)
   {
   if (GL.adjList[i]._in == 0)
   {
   stack.Push(i);
   }
   }
   while (stack.Count != 0)
   {
   gettop = stack.Pop();//出栈
   Console.WriteLine(GL.adjList[gettop].data);//打印此结点
   count++; //统计输出顶点数
   for (e = GL.adjList[gettop].firstNode; e != null; e = e.next)
   {//对此顶点弧表遍历
   k = e.adjvex;
   if ((--GL.adjList[k]._in) == 0)
   {//将k号顶点的入度减去1,若入度变成0,则入栈,便于下次循环
   stack.Push(k);
   }
   }
   }
   if (count < GL.numVertexes) //count小于顶点数,则没有遍历完,说明是有环有向图
   return false;
   else
   return true;
   }
  
   /// <summary>
   /// 构造邻接表
   /// </summary>
   /// <returns></returns>
   public static Graph InitGraph()
   {
   VertexNode[] adjList = new VertexNode[14]
   {
   new VertexNode(0, "V0", new EdgeNode()),
   new VertexNode(0, "V1", new EdgeNode()),
   new VertexNode(2, "V2", new EdgeNode()),
   new VertexNode(0, "V3", new EdgeNode()),
   new VertexNode(2, "V4", new EdgeNode()),
   new VertexNode(3, "V5", new EdgeNode()),
   new VertexNode(1, "V6", new EdgeNode()),
   new VertexNode(2, "V7", new EdgeNode()),
   new VertexNode(2, "V8", new EdgeNode()),
   new VertexNode(2, "V9", new EdgeNode()),
   new VertexNode(1, "V10", new EdgeNode()),
   new VertexNode(2, "V11", new EdgeNode()),
   new VertexNode(1, "V12", new EdgeNode()),
   new VertexNode(2, "V13", new EdgeNode())
   };
   adjList[0].firstNode.next = new EdgeNode(11, new EdgeNode(5, new EdgeNode(4, null)));
   adjList[1].firstNode.next = new EdgeNode(8, new EdgeNode(4, new EdgeNode(2, null)));
   adjList[2].firstNode.next = new EdgeNode(9, new EdgeNode(6, new EdgeNode(5, null)));
   adjList[3].firstNode.next = new EdgeNode(13, new EdgeNode(2, null));
   adjList[4].firstNode.next = new EdgeNode(7, null);
   adjList[5].firstNode.next = new EdgeNode(12, new EdgeNode(8, null));
   adjList[6].firstNode.next = new EdgeNode(5, null);
   adjList[7].firstNode = null;
   adjList[8].firstNode.next = new EdgeNode(7, null);
   adjList[9].firstNode.next = new EdgeNode(11, new EdgeNode(10, null));
   adjList[10].firstNode.next = new EdgeNode(13, null);
   adjList[11].firstNode = null;
   adjList[12].firstNode.next = new EdgeNode(9, null);
   adjList[13].firstNode = null;
   Graph GL = new Graph();
   GL.adjList = adjList;
   GL.numVertexes = 14;
   GL.numEdges = 20;
   return GL;
   }
   }
  }
  

对于一个具有n个顶点e条弧的AOV网来说,时间复杂度为:O(n+e)。

八、关键路径

1、AOE网(Activity On Edge NetWork)
在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,称为AOE网。
关键路径,把路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。
882c756666b48c893f8faa0e9ae9f3f7.png

AOE和AOV网,尽管两者都是用来对工程建模的,但它们还是有很大的不同,主要体现在AOV网是用顶点表示活动,它只描述活动之间的制约关系,而AOE网是用边表示活动的网,边上的权值表示该活动持续的时间。
  关键路径算法原理:
  a. 事件的最早发生时间 etv(earliest time of vertex):即顶点vk的最早发生时间。
  b. 事件的最晚发生时间 ltv(latest tim of vertex):即顶点vk的最晚发生时间。
  c. 活动的最早开工时间ete(earliest time of edge):即弧ak的最早发生时间。
  d. 活动的最晚开工时间lte(latest time of edge):即弧ak的最晚发生时间。
  由a和b可以求得c和d,再根据ete[k]是否与lte[k]相等来判断ak是否是关键活动。
  关键路径算法:

  -----------------------------------------
  using System;
  using System.Collections.Generic;
  
  
  /// <summary>
  /// 拓扑排序
  /// </summary>
  namespace AOE网关键路径
  {
   public class EdgeNode //边表结点
   {
   public int adjvex; //邻接点域,储存该顶点对应的下标
   public int weight; //储存权值,对于非网图不需要
   public EdgeNode next; //链域,指向下一个邻接点
  
   /// <summary>
   /// 空构造
   /// </summary>
   public EdgeNode()
   {
   }
  
   public EdgeNode(int adjvex, EdgeNode next)
   {
   this.adjvex = adjvex;
   this.next = next;
   }
  
   public EdgeNode(int adjvex, int weight, EdgeNode next)
   {
   this.adjvex = adjvex;
   this.weight = weight;
   this.next = next;
   }
   }
  
   public class VertexNode //顶点表结点
   {
   public int _in; //顶点入度
   public string data; //顶点域,存储顶点信息
  
   public EdgeNode firstNode; //边表头指针
  
   public VertexNode(int @in, string data, EdgeNode firstNode)
   {
   _in = @in;
   this.data = data;
   this.firstNode = firstNode;
   }
   }
  
   public class Graph
   {
   public VertexNode[] adjList; //顶点表集合
   public int numVertexes, numEdges; //顶点数和边数
   }
  
   public class Program
   {
   public int[] etv, ltv; //事件最早发生时间和最迟发生时间数组
   public Stack<int> stack2 = new Stack<int>();//用于存储拓扑序列的栈
   public int top2; //用于stack2的指针
  
  
  
   /// <summary>
   /// 拓扑排序,若GL无回路,则输出拓扑排序序列并返回true,否则返回false
   /// </summary>
   /// <param name="GL">有向图</param>
   /// <returns></returns>
   public bool TopologgicSort(Graph GL)
   {
   EdgeNode e;
   int i, k, gettop;
   // int top = 0; //用于栈指针下标
   int count = 0; //用于统计输出顶点的个数
   Stack<int> stack = new Stack<int>();
   //将遍历所有顶点,将入度为0的顶点入栈
   for (i = 0; i < GL.numVertexes; i++)
   {
   if (GL.adjList[i]._in == 0)
   {
   stack.Push(i);
   }
   }
   top2 = 0; //初始化为0
   etv = new int[GL.numVertexes];
   for ( i = 0; i < GL.numVertexes; i++)
   {
   etv[i] = 0;//初始化为0
   }
   stack2 = new Stack<int>();
   while (stack.Count != 0)
   {
   gettop = stack.Pop();//出栈
   Console.WriteLine(GL.adjList[gettop].data);//打印此结点
   count++; //统计输出顶点数
   stack2.Push(gettop);//将弹出的顶点序号压入拓扑序列的栈
   for (e = GL.adjList[gettop].firstNode; e != null; e = e.next)
   {//对此顶点弧表遍历
   // Console.WriteLine(e.adjvex);
   k = e.adjvex;
   if (k==0)
   {
   continue;
   }
   // Console.WriteLine(k);
   if ((--GL.adjList[k]._in) == 0)
   {//将k号顶点的入度减去1,若入度变成0,则入栈,便于下次循环
   stack.Push(k);
   }
   if (etv[gettop]+ e.weight > etv[k])//求各顶点事件最早发生时间
   {
   etv[k] = etv[gettop] + e.weight;
   }
   }
   }
   foreach (var item in etv)
   {
   Console.WriteLine(item.ToString());
   }
   if (count < GL.numVertexes) //count小于顶点数,则没有遍历完,说明是有环有向图
   return false;
   else
   return true;
   }
  
   /// <summary>
   /// 构造邻接表
   /// </summary>
   /// <returns></returns>
   public Graph InitGraph()
   {
   VertexNode[] adjList = new VertexNode[10]
   {
   new VertexNode(0, "V0", new EdgeNode()),
   new VertexNode(1, "V1", new EdgeNode()),
   new VertexNode(1, "V2", new EdgeNode()),
   new VertexNode(2, "V3", new EdgeNode()),
   new VertexNode(2, "V4", new EdgeNode()),
   new VertexNode(1, "V5", new EdgeNode()),
   new VertexNode(1, "V6", new EdgeNode()),
   new VertexNode(2, "V7", new EdgeNode()),
   new VertexNode(1, "V8", new EdgeNode()),
   new VertexNode(2, "V9", new EdgeNode()),
   };
   adjList[0].firstNode.next = new EdgeNode(2, 4, new EdgeNode(1, 3, null));
   adjList[1].firstNode.next = new EdgeNode(4, 6, new EdgeNode(3, 5, null));
   adjList[2].firstNode.next = new EdgeNode(5, 7, new EdgeNode(3, 8, null));
   adjList[3].firstNode.next = new EdgeNode(4, 3, null);
   adjList[4].firstNode.next = new EdgeNode(7, 4, new EdgeNode(6, 9, null));
   adjList[5].firstNode.next = new EdgeNode(7, 6, null);
   adjList[6].firstNode.next = new EdgeNode(9, 2, null);
   adjList[7].firstNode.next= new EdgeNode(8, 5, null);
   adjList[8].firstNode.next = new EdgeNode(9, 3, null);
   adjList[9].firstNode.next = null;
   Graph GL = new Graph();
   GL.adjList = adjList;
   GL.numVertexes = 10;
   GL.numEdges = 13;
   return GL;
   }
  
   /// <summary>
   /// 求关键路径,GL为有向网,输出GL的各项关键活动
   /// </summary>
   /// <param name="GL"></param>
   public void CriticalPath(Graph GL)
   {
   EdgeNode e;
   int i, gettop, k, j;
   int ete, lte; //声明活动的最早发生时间和最迟发生时间变量
   TopologgicSort(GL);//求拓扑序列,计算数组etv和stack2的值
   ltv = new int[GL.numVertexes];//事件最晚发生时间
   for ( i = 0; i < GL.numVertexes; i++)
   {
   ltv[i] = etv[GL.numVertexes - 1];//初始化ltv
   }
   while (stack2.Count != 0)
   {
   gettop = stack2.Pop(); //将拓扑序列出栈,后进先出
   for (e = GL.adjList[gettop].firstNode;e!=null;e = e.next)
   {//求各个顶点事件的最迟发生时间ltv值
   k = e.adjvex;
   if (ltv[k]-e.weight < ltv[gettop])
   {
   ltv[gettop] = ltv[k] - e.weight;
   }
   }
   }
   for (j = 0; j< GL.numVertexes; j++)
   {
   for (e = GL.adjList[j].firstNode;e!=null;e = e.next)
   {
   k = e.adjvex;
   ete = etv[j]; //活动最早发生时间
   lte = ltv[k] - e.weight;//活动最迟发生时间
   // Console.WriteLine($"{ete} ,{lte}" );
   if (ete ==lte && j!=k) //两者相等,即再关键路径上
   {
   Console.WriteLine($"<{GL.adjList[j].data},{GL.adjList[k].data}> {e.weight}");
  
   }
   }
   }
   }
   }
  }
  ---------------------------------------------------
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  
  namespace AOE网关键路径
  {
   class MainClass
   {
   static void Main(string[] args)
   {
   Program pro = new Program();
   //TopologgicSort(InitGraph());
   pro.CriticalPath(pro.InitGraph());
   Console.ReadLine();
   }
   }
  }
  -------------------------------------------------

时间复杂度:O(n+e)
  关键路径可以有多条

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值