首页 > 其他分享 >数据结构学习记录(三)

数据结构学习记录(三)

时间:2023-09-18 12:14:38浏览次数:54  
标签:dist 记录 int VertexNum Vertex 学习 邻接 顶点 数据结构

一、知识要点

1、图的基本概念

  • 图的定义和术语

    • 图的定义

      • 图(Graph)是由两个集合构成,一个是非空但有限的顶点集合V,另一个是表述顶点之间边的集合E(可能是$\emptyset$)。图可表示为G = (V ,E ).
      • 每条边是一顶点对(v, w)且v,w $\in$ V。通常用|V|表示顶点的数量,|E|表示边的数量。
      • 在图中,至少要有一个顶点,但边集可以为空。
    • 图的相关术语

      • 无向图:无向图中顶点之间的所有边都可互通,不用标方向,起点和终点次序并不重要。用圆括号(v, w)表示无向图

      • 有向图:有向图中所有边都有方向,即<v, w>不同于<w, v>,无向图用尖括号<>表示

      • 简单图:所有边不重合的图叫简单图。

      • 邻接点:在无向图中,一条边连接的两个顶点叫邻接点;在有向图中,如果<v, w>是一条边,那么可以说起点v邻接到终点w

      • 路径、简单路径和回路

        • 从顶点v沿着边可到顶点w,那么这些边就是路径,路径长度是这条路径所包含的边数
        • 在路径序列中,顶点不重复出现的路径称为简单路径
        • 第一个顶点和最后一个顶点相同的路径称为回路或环。若一个图有n个顶点,并且有大于n − 1条边,则此图一定有环。
        • 除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路称为简单回路
      • 无向完全图:在无向图中,任意两个结点都有边相连,那么这个无向图称为无向完全图。可以证明:如果有n个结点,那么有n(n - 1) / 2条边。

      • 有向完全图:在有向完全图中任意两个顶点之间都存在方向相反的两条弧。可以证明:如果有n个结点,那么有n(n - 1) 条边。

      • 顶点的度、入度和出度:顶点的度是指依附于某节点的边数。在有向图中,一某顶点为终点的边数称为入度,反之则为出度。度 = 入度 + 出度。

      • 稠密图、稀疏图

      • 边数很少的图称为稀疏图,反之称为稠密图。稀疏和稠密本身是模糊的概念,稀疏图和稠密图常常是相对而言的。一般当图G满足∣ E ∣ < ∣ V ∣ l o g ∣ V ∣时,可以将G视为稀疏图。

      • 权、网图:在一个图中,每条边都可以标上具有某种含义的数值,该数值称为该边的权值。这种边上带有权值的图称为带权图,也称

      • 子图:设有两个图G = ( V , E ) 和G ′ = ( V ′ , E ′ ), 若V ′是V的子集,且E ′ 是E的子集,则称G ′是G的子图。

      • 连通、连通图和连通分量:在无向图中,若从顶点v到顶点w有路径存在,则称v和w是连通的若图G中任意两个顶点都是连通的,则称图G为连通图,否则称为非连通图。无向图中的极大连通子图称为连通分量。若一个图有n个顶点,并且边数小于n − 1,则此图必是非连通图。

      • 强连通图、强连通分量在有向图中,若从顶点v到顶点w和从顶点w到项点v之间都有路径,则称这两个顶点是强连通的。若图中任何一对顶点都是强连通的,则称此图为强连通图。有向图中的极大强连通子图称为有向图的强连通分量。

      • 生成树:连通图的生成树是包含其所有结点的一个极小联通子图,若图中结点数为n,那么其生成树有n-1条边。在非连通图中,连通分量的生成树构成了非连通图的生成森林。

  • 图的抽象结构

    • 类型名称:图(Graph)
    • 数据对象集:一非空的顶点集合Vertex和一个边集合Edge,每条边用对应的一对定点表示。
    • 操作集:对于任意的图G $\in$ Graph, 顶点V $\in$ Vertex, 边E $\in$ Edge, 以及任一访问顶点的函数Visit( ),我们主要有以下操作
      • Graph Create_Graph(int Vertex_Num):构造一个有Vertex_Num个结点但没有边的图。
      • void Insert_Edge(Graph G, Edge E):在G中增加新边E。
      • void Delete_Edge(Graph G, Edge E):在G中删除边E。
      • bool Is_Empty(Graph G):如果G为空则返回true,否则返回false。
      • void DFS(Graph G, Vertex V, (*Visit)(Vertex)):在图G中,从顶点V出发进行深度优先遍历
      • void BFS(Graph G, Vertex V, (*Visit)(Vertex)):在图G中,从顶点V出发进行广度优先遍历

2、图的储存结构

  • 邻接矩阵

    • 图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图结点数据信息,一个二维数组(邻接矩阵)储存图中的边的信息。

    • 假设邻接矩阵A是一个n * n的方阵,若(vi, vj)或<vi, vj>是图中的边,那么A[i] [j]为1,否则为0.

    • 在无向图中:

      • 无向图的邻接矩阵一定是一个对称矩阵(即从矩阵的左上角到右下角的主对角线为轴,右上角的元与左下角相对应的元全都是相等的)。 因此,在实际存储邻接矩阵时只需存储上(或下)三角矩阵的元素。
      • 对于无向图,邻接矩阵的第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点的度 TD(vi)。比如顶点v1的度就是1 + 0 + 1 + 0 = 2
      • 求顶点vi 的所有邻接点就是将矩阵中第i行元素扫描一遍, A [ i ] [ j ]为 1就是邻接点。
    • 在有向图中

      • 主对角线上数值依然为0。但因为是有向图,所以此矩阵并不对称。
      • 有向图讲究入度与出度,顶点v1的入度为1,正好是第v1列各数之和。顶点v1的出度为2,即第v1行各元素之和
    • 在网图中,A[ i ] [ j ]存的是边的权值。

    • //邻接矩阵的存储结构
      #define MaxVertexNum 100	//定义顶点的最大数
      typedef char VertexType;	//顶点的数据类型
      typedef int EdgeType;	//带权图中边上权值的数据类型
      struct GNode
      {
          VertexType Vex[MaxVertexNum];	//顶点表
          EdgeType Edge[MaxVertexNum][MaxVertexNum];`//邻接矩阵,边表
          int vexnum;	//顶点数
          int arcnum;	//边数
      };
      typedef struct GNode *MGraph;	//图类型
      
      //创建图
      
      #defind IFINITY 65535	//将无穷设为无符号整数最大值
      typedef int Vertex;	//用顶点下标表示顶点,为整型
      
      //边的定义
      struct ENode
      {
          Vertex V1, V2;	//有向边<V1, V2>
          WeightType Weight;	//边的权值
      };
      typedef struct ENode *Edge;
      
      //初始化一个有VertexNum个顶点但没有边的图
      MGraph CreateGragh(int VertexNum)
      {
          Vertex V, W;
          MGraph Graph;
          
          Graph = (MGraph)malloc(sizeof(struct GNode));	//建立图
          Graph->vexnum = VertexNum;
         	Graph->arcnum = 0;
          //初始化邻接矩阵
          for(V = 0; V < Graph->vexnum; V++)
              for(W = 0; W < Graph->arcnum; W++)
                  Graph->G[V][W] = INFINITY;
          
          return Graph;
      }
      
      //插入边
      void InsertEdge(MGraph Graph, Egde E)
      {
          Graph->G[E->V1][E->V2] = E->Weight;
      }
      
      //构建图
      MGraph BuildGraph()
      {
          MGraph Graph;
          Edge E;
          Vertex V;
          int Nv, i;
          
          printf("输入顶点个数:");
          scanf("%d", &Nv);
          
          Graph = CreateGraph(Nv);	//初始化图
          
          printf("输入边数:");
          scanf("%d", &(Graph->arcnum));
          if(Graph->arcnum != 0)	//如果有边
          {
              E = (Edge)malloc(sizeof(struct ENode));
              //读入边
              for(i = 0; i < Graph->arcnum; i++)
              {
                  printf("输入起点、终点、权重:");
                  scanf("%d %d %d", &E->V1, &E->V2, &E->Weight);
                  //插入边
                  InsertEdge(Graph, E);
              }
          }
          //读入顶点数据
          for(V = 0; V < Graph->vexnum; V++)
          {
              printf("输入顶点数据:");
              scanf("%c", &Graph->Vex[V]);
          }
          return Graph;
      }
      
    • 稠密图适合使用邻接矩阵的存储表示。

  • 邻接表

    • 邻接表是图的一种顺序储存与链式储存相结合的储存方法
  • 邻接表有两种结点结构:

    • 顶点表:由顶点数据域(Data)指向第一条邻接边的指针域(FirstEdge)构成。

    • 边表:由邻接点域(AdjV)指向下一条邻接边的指针域(Next)构成。如果是网图结点,还需要增设一个权值域(Weight)。

    • 顶点表用数组按下标存储,每个顶点将所有邻接于该顶点的其他顶点链成一个单链表。

    • 图的邻接表储存特点

      • 若无向图有V个顶点和E条边,那么他的邻接表需要V个头节点和2E个表结点,如果图相当稀疏的话,用邻接表比邻接矩阵节省空间
    • 若要确定给定的两个顶点间是否存在边,则在邻接矩阵中可以立刻查到,而在邻接表中则需要在相应结点对应的边表中查找另一结点,效率较低

      • 在有向图的邻接表表示中,求一个给定顶点的出度只需计算其邻接表中的结点个数;但求其顶点的入度则需要遍历全部的邻接表。
    • #define MaxVertexNum 100	//最大顶点数设为100
      typedef int Vertex;		//用顶点下标表示顶点
      typedef int WeightType;		//边的权值设为整型
      typedef char DataType;		//顶点存储数据类型设为字符型
      
      //边的定义
      struct ENode
      {
          Vertex V1, V2;		//边<v1, v2>
          WeightType Weight;	//边的权值
      };
      typedef struct ENode *Edge;		//设置为边结构
      
      //边表的定义
      struct AdjVNode
      {
          Vertex V;				//顶点下标
          WeightType Weight;		//边权值
          struct AdjVNode *Next;	//指向下一邻接结点的指针
      };
      typedef struct AdjVNode *AdjV;	//设置为邻接点结点结构
      
      //顶点表头定义
      struct VNode
      {
          AdjV FristEdge;		//指向下一邻接结点的指针
          DataType Data;		//结点权值
      };
      typedef struct VNode AdjList[MaxVertexNum];	//设置为邻接表结构
      
      //图节点定义
      struct GNode
      {
          int VertexNum;	//顶点数
          int EdgeNum;	//边数
          AdjList AL;		//邻接表
      };
      typedef struct GNode *LGraph;	//设置为邻接表图类型
      
      //创建没有边的图
      LGraph CreateLGraph(int VertexNum)
      {
          int i;
          LGraph G;	//创建图
          G = (LGraph)malloc(sizeof(strcut GNode));	//赋予空间
          G->VertexNum = MaxVertexNum;	//输入顶点数
          G->EdgeNum = 0;	//边为0
          //给每个表头置空
          for(i = 0; i < G->VertexNum; i++)
              G->AL[i] = NULL;
          
          return G;
      }
      
      //插入边
      void InsertEdge(LGraph G, Edge E)
      {
          //当图为有向图时,插入边<v1, v2>
          AdjV NewNode;	//建立V2结点
          NewNode = (AdjV)malloc(sizeof(struct AdjVNode));	//赋予空间
          NewNode->V = E->V2;		//存入下标
          NewNode->Weight = E->Weight;	//存入边权值
          //将邻接结点插入头节点V1
          NewNode->Next = G->AL[E->V1].FirstEdge;
          G->AL[E->V1].FirstEdge = NewNode;
          
          //如果图为无向图,还需把V1插入V2
          AdjV New2;
          New2 = (AdjV)malloc(sizeof(struct AdjVNode));
          New2->V = E->V1;
          New2->Weight = E->Weight;
          New2->Next = G->AL[E->V2].FirstEdge;
          G->AL[E->V2].FirstEdge = New2;
      }
      
      //创建图·
      LGraph BuildLGraph()
      {
          int i, VertexNum;
          LGraph G;
          Edge E;
          
          printf("请输入顶点数:");
          scanf("%d", &VertexNum);
          G = CreateLGraph(VertexNum);
          
          printf("请输入边数:");
          scanf("%d", &G->EdgeNum);
          
          if(G->EdgeNum != 0)
          {
              E = (Edge)malloc(sizeof(struct ENode));
              for(i = 0; i < G->EdgeNum; i++)
              {
                  scanf("%d%d%d", &E->V1, &E->V2, &E->Weight);
                  InsertEdge(G, E);
              }
          }
          
          return Graph;
      }
      
    
    

3、图的遍历

  • 深度优先搜索

    • 深度优先搜索也称DFS算法,它类似于树的先序遍历,他从某个顶点v0出发,依次递归探索未被探索的顶点。

    • //假设图以邻接表的形式储存
      
      //Visited数组记录顶点的探索情况,预先将所有顶点设置为未探索
      bool Visited[MaxVertexNum] = {false};
      
      //访问顶点函数
      void Visit(Vertex V)
      {
          printf("正在访问结点%d\n", V);
      }
      
      //DFS函数深度优先搜索
      void DFS(LGraph G, Vertes V)
      {
          //从顶点V出发对图G进行DFS搜索
          AdjV W;
          
          //访问节点V
          Visit(V);
          //标记该节点
          Visited[V] = true;
          
          //对V的邻接点进行访问
          for(W = G->AL[V].FirstEdge; W; W->Next)
          {
              //当顶点未被访问时
              if(Visited[W->V] == false)
                  //递归访问
                  DFS(G, W->V);
          }
      }
      
  • 广度优先搜索

    • 广度优先搜索也称BFS算法,它类似于树的按层次遍历的过程,就是构造一个队列,每访问一个顶点就把该顶点的所有邻接顶点放入队列。

    • //假设图以邻接矩阵的方式存储
      
      //Visited数组记录顶点的探索情况,预先将所有顶点设置为未探索
      bool Visited[MaxVertexNum] = {false};
      
      //判断<V,W>是否是边
      bool IsEdge(MGraph G, Vertex V, Vertex W)
      {
          return (G->Edge[V][W] < INFINITY ? true : false);
      }
      
      //以S为出发点对图进行BFS搜索
      void BFS(MGraph G, Vertex S)
      {
          Queue Q;
          Vertex V, W;
          
          //创建一个空队列
          Q = CreateQueue();
          Visit(S);	//访问该顶点
          //标记该顶点
          Visited[S] = true;
          //S入队
          AddQ(Q, S);
          
          //层序遍历顶点
          while(!IsEmpty(Q))
          {
              //当Q不为空时
              V = DeleteQ(Q);	//弹出顶点
              //找到Q的所有邻接点放入队列
              for(W = 0; W < MGraph->VertexNum; W++)
              {
                  if(!Visit[W] && IsEdge(G, V, W))
                  {
                      Visit(W);
                      Visited[W] = true;
                      AddQ(W);
                  }
              }
          }
      }
      

4、最小生成树

  • 生成树的构建与最小生成树的概念

    • 对连通图不同的遍历,就可能得到不同的生成树。
    • 如果无向连通图是一个网图,那么它的所有生成树中必有权值总和最小的生成树,称为最小生成树(MST),当然最小生成树也未必是唯一的l
    • 有两种常用的构造最小生成树的方法:Prim算法和Kruskal算法。
  • 构建最小生成树的Prim算法

    • 从一个顶点出发,在保证不形成回路的前提下,每找到并添加一条最短的边,就把当前形成的连通分量当做一个整体或者一个点看待,然后重复“找最短的边并添加”的操作。

    • 原图为稠密图的时候用Prim算法好一点

    • //最小生成树肯定是稀疏图,所以最小生成树用邻接表储存
      //这里用邻接矩阵储存原图
      
      #defind ERROR -1	//错误标记,表示生成树不存在
      
      //prime算法,将图的最小生成树保存到邻接表储存的图MST中,返回最小权值或错误信息
      int Prim(MGraph G, LGraph MST)
      {
          //定义一个数组用来存放下标代表的顶点到其父结点的权值
          //定义最小权值和
          WeightType dist[MaxVertexNum], TotalWeight = 0;
          //定义一个数组来存放下标代表的顶点的父结点
          Vertex parant[MaxVertexNum], V, W;
          int VCount;		//VCount表示收录顶点的数量
          Edge E;
          
          //将所有顶点的父结点初始化为V0,并把每个顶点到v0的权值存入
          for(V = 0; V < G->VertexNum; V++)
          {
              dist[V] = G->Edge[0][V];
              parant[V] = 0;
          }
          VCount = 0;
          
          //创建一个空的邻接表来表示MST
          MST = CreateLGragh(G->VertexNum);
          E = (Edge)malloc(sizeof(struct ENode));
          
          dist[0] = 0;	//表示V0已被收录
          VCount++;
          parant[0] = -1;		//表示当前树根为V0
          
          //核心代码,收录最小权值并更新未收录顶点的父结点
          while(1)
          {
              V = FindMinDist(G, dist);	//找到dist中的最小权值
              if(V == ERROR)
                  break;	//如果最小权值不存在,则结束算法
              //将V和相应的边<parant[V], V>收录进MST
              E->V1 = parent[V];
              E->V2 = V;
              E->Weight = dist[V];
              InsertEdge(MST, E);
              TotalWeight += dist[V];
              dist[V] = 0;	//标记V顶点已被收录
              VCount++;
              
              //找到与当上一个被收录顶点相邻的的所有未收录顶点
              //如果有未收录顶点到上一个被收录节点的边权值比dist数组存的数小,那么更新dist数组,并更新该未收录顶点的父结点
              for(W = 0; W < G->VertexNum; V++)
              {
                  if(G->Edge[V][W] < dist[W])
                  {
                      dist[W] = G->Edge[V][W];
                      parant[W] = V;
                  }
              }
          }//while结束
          
          //如果收录的顶点未满就返回错误信息
          if(VCount < G->VertexNum)
              return ERROR;
          //否则返回最小权值总和
          return TotalWeight;
      }
      
      //返回dist中未收录顶点最小顶点
      Vertex FindMinDist(MGraph G, WeightType dist[])
      {
          Vertex MinV, V;
          WeightType MinDist = INFINITY;
          
          for(V = 0; V < G->VertexNum; V++)
          {
              if(dist[V] != 0 && dist[V] < MinDist)
              {
                  MinDist = dist[V];
                  MinV = V;
              }
          }
          if(MinDist < INFINITY)
              return MinV;
          else
              return ERROR;
      }
      
  • 构建最小生成树的Kruskal算法

    • 当原图是稀疏图时,用Kruskal算法比prim算法好

    • Kruskal算法是一种按权值的递增次序选择合适的边来构造最小生成树,所以此算法是选择最小的边来构造的。

    • //这里用邻接表储存原图和最小生成树
      
      int Kruskal(LGraph G, LGraph MST)
      {
          //初始化空MST
          MST = CreateLGraph(G->VertexNum);
          //设置权重和
          WeightType Vcount = 0;
          //设置parant数组,并将其初始化
          int parant[G->VertexNum] = -1;
          //设置一个边集,把图的所有边存入边集
          Edge E[G->EdgeNum];
          CreateEdge(G, E);
          //构造一个包含原图所有边的最小堆
          MinHeap H = BuildMinHeap(E);
          //while循环构造最小生成树
          while(MST->EdgeNum < G->Vertex - 1 && G->EdgeNum != 0)
          {
              //从最小堆取一个边
              Edge E0 = DeleteMinHeap(H);
              //原图边集中删掉这个边
              DeleteEdge(G, E0);
              //确认这个边是否会让MST形成回路
              if(!Find(parant, E0))
              //是则丢弃该边
              //否则将这个边加入MST,并标记顶点
              {
                  InsertLGraph(MST, E);
                  if(parant[E->V1] == -1)
                      parant[E->V1] = E->V2;
                  if(parant[E->V2] == -1)
                      parant[E->V2] = E->V1;
              }
              //增加权重和
              Vcount += E->Weight;
              //增加MST收集的边数
          }
          //如果MST收集的边不足则返回错误信息
          if(MST->EdgeNum != G->NertexNum)
              return ERROR;
          else
              return Vcount;
          //否则返回权重和
      }
      
      //将图G的边存入边集E中
      void CreateEdge(LGraph G, Edge E[])
      {
          Vertex V, W = 0;
          for(V = 0; V < G->VerNum; V++)
          {
              AdjV H = G->AL[V]->FirstEdge;
              while(H != NULL)
              {
                  E[W]->V1 = V;
              	E[W]->V2 = H->V;
                  E[W]->Weight = H->Weight;
                  W++;
                  H = H->Next;
              }
          }
      }
      
      //构造一个包含原图所有边的最小堆
      MinHeap BuildMinHeap(E[]);
      {
          MinHeap H = CreateMinHeap(G->EdgeNum);
          int i;
          for(i = 0; i < G->EdgeNum; i++)
          {
              H->Data[i] = E[i];
          }
          return H;
      }
      

5、最短路径

  • 单源最短路径

    • 从一个源点到其他各项顶点最短路径问题称为“单源最短问题”,该问题可描述为图G中的顶点V0到其余各顶点的最短路径。

    • Dijkstra算法可以求单源最短路径,这个算法和prim算法几乎一样,都是用dist数组存最短边,parent数组存父结点

    • //利用Dijkstra算法求单源最短路径
      //这里图用邻接矩阵存储
      #defind ERROR -1	//设置报错符号
      #defind INF	65535	//指定权值为无穷的边为INF
      
      //构建最短路径结构
      struct MLNode
      {
          //源顶点和终顶点
          Vertex HomeV;
          Vertex EndV;
          //包含最短路径中所有经过的顶点的数组
          Vertex *SumV;
          //顶点个数
          int VerNum;
          //路径总长度
          WeightType SumLength;
      };
      typedef struct MLNode *MinL;
      //求最短路径函数,函数形参是图、起始顶点和最终顶点
      MinL MinLength(MGraph G, Vertex HV, Vertex EV)
      {
          //创建一个长度为G->VertexNum的一个parent数组,用来存每个顶点对应的父结点
          int parent[G->VertexNum];
          //创建一个长度为VertexNum的一个dist数组,用来存每个顶点到其父结点的权值
          int dist[G->VertexNum];
          //利用Dijkatra算法得到parent数组和dist中的元素
          Dijkatra(G, HV, dist, parent);
          //调用FindSumV函数得到MinL->SumV和Min->VerNum
          Vertex *SV;
          int VN = FindSumV(EV, parent, SV);
          //调用FindSumL函数得到SumLength
          WeightType SL = FindSumL(dist, SV);
          //初始化MinL
          MinL ML = CreateMinL(ML, HV, EV, SV, VN, SL);
          return ML;
      }
      
      //Dijkatra算法,构造dist数组和parent数组
      void Dijkatra(MGraph G, Vertex HV, int dist[], int parent[])
      {
          Vertex V, W;
          //设置一个mark数组,用来标记已收录的顶点
          int mark[G->VertexNum];
          //初始化dist数组,将每个顶点到源顶点的权值存入
          //初始化parent数组,将与源节点邻接的顶点的父结点设为源节点,不邻接的设为-1
          //初始化mark数组,将所有顶点设置为未收录
          for(V = 0; V < G->VertexNum; V++)
          {
              dist[V] = G->Edge[HV][V];
              if(dist[V] < INF)
                  parent[V] = HV;
              else
                  parent[V] = -1;
              mark[V] = 0;
          }
          //标记源顶点
          dist[HV] = 0;
          mark[V] = 1;
          //利用while函数循环收录顶点
          while(1)
          {
              //调用FindMinDist函数找与父结点最小邻接边的顶点V
              V = FindMinDist(G, dist, mark);
              //没找到则结束循环
              if(V == ERROR)
                  break;
              //找到了则标记该顶点V已收录
              mark[V] = 1;
              //利用for循环找能够让dist变小的顶点
              for(W = 0; W < G->VertexNum; W++)
              {
                  //如果循环到的邻接顶点的邻接边小于其父结点的邻接边
                  if(G->Edge[V][W] < dist[W])
                  {
                      //将该边替换
                      dist[W] = G->Edge[V][W];
                      //更新parent数组
                      parent[W] = V;
                  }
              }
          }
      }
      
      //从dist数组找未收录最小顶点
      Vertex FindMinDist(MGraph G, dist[], mark[])
      {
          int i, minV = 0;
          int flag = 0;
          for(i = 0; i < G->VertexNum; i++)
          {
              if(mark[i] && dist[minV] > dist[i])
              {
                  flag = 1;
                  minV = i;
              }
          }
          if(flag == 1)
              return minV;
          else
              return ERROR;
      }
      
  • 每一对顶点之间的最短路径

    • 在稠密图中,求得每一顶点最短路径有一个算法比Dijkatra算法效率更高,也更简单,他是Floyd算法

    • Floyd算法的大概流程是

      • 找一个初始顶点H,如果某个从A到B的路径比先A到H,再H,到B的路径长,那就可以说H是A到B的一个中转顶点,然后更新A到B的最短路径
      • 依次用H结点循环所有起点到终点的路径,就可以得到初级的每一个顶点之间的最短路径。
      • 然后再找别的顶点看看能不能充当初级最短路径图的中转顶点,然后又更新初级路径图,从此往复,直到循环完所有顶点,找到每对顶点最终最短路径图,和最终中转顶点表
    • //Floyd算法求各个顶点最短路径
      //这里图用邻接矩阵储存
      void Floyd(MGraph G, WeightType dis[][MaxVertexNum], Vertex path[][MaxVertexNum])
      {
          Vertex i, j, K;	
          
          //初始化最短路径图dis,将图G复制到dis
          for(i = 0; i < G->VertexNum; i++)
              for(j = 0; j < G->VertexNum; j++)
              {
                  dis[i][j] = G->Edge[i][j];
                  //初始化中转顶点表path
                  path[i][j] = -1;
              }
          
          //循环每个中转顶点
          for(k = 0; k < G->VertexNum; k++)
              //循环每条邻接边,找到他们的中转顶点
              for(i = 0; i < G->VertexNum; i++)
                  for(j = 0; j < G->VertexNum; j++)
                  {
                      //如果i到j的路径比i到k再到j的路径长,就更新i到j的路径
                      if(dis[i][k] + dis[k][j] < dis[i][j])
                      {
                          dis[i][j] = dis[i][k] + dis[k][j];
                          //更新中转顶点
                          path[i][j] = k;
                      }
                  }
          //最终path[i][j]存的中转顶点是邻接于顶点i的
      }
      
      //利用中转顶点表来找最短路径路程
      void Find(MGraph G, path[][MaxVertexNum], Vertex VH, Vertex VE);
      {
          printf("%d ", HV);
          Vertex k = path[VH][VE];
          while(K != -1)
          {
              printf("-> %d ", k);
              k = path[k][HE];
          }
          printf("-> %d\n", HE);
      }
      

6、拓扑排序

  • 拓扑排序是对有向无环图进行排序,它的排序目的是为了排列顶点的先后顺序

  • 拓扑排序的基本步骤:找到任意一个入度为0的顶点,并从图中删除该顶点以及与其相邻的所有边,然后对改变后的图重复此操作。如果每个顶点入度都大于一,那么必定存在回路。

  • 可用队列来储存入度为0的顶点

  • //拓扑排序排列有向无环图
    //这里用邻接表表示图
    bool TopSort(LGraph G, Vertex TopOrder[])
    {
        //对图G进行拓扑排序,排序后的顶点放入TopOrder数组
        //遍历图,得到所有顶点的入度,并存入Indegree数组
        Vertex Indegree[G->VertexNum];
        Vertex V;
        AdjV W;
        for(V = 0; V < G->VertexNum; V++)
        	for(W = G->AL[V].FirstEdge; W == NULL; W = W->Next)
            {
                Indegree[W->V]++;
            }
        //创建队列,并将所有入度为0的顶点入队
        Queue Q = CreateQueue(G->VertexNum);
        for(V = 0; V < G->VertexNum; V++)
        {
            if(Indegree[V] == 0)
                AddQ(Q, V);
        }
        //核心代码
        //设置一个计数器来观察所有出过队的顶点数量
        int cnt = 0;
        //while循环来实现拓扑排序
        while(!IsEmpty(Q))	//当队列为空时说明没有入度为0的顶点,退出循环
        {
            //出队一个顶点,把它存入TopOrder数组,并将计数器加一
            V = DeleteQ(Q);
            TopOrder[cnt] = V;
            cnt++;
            //把这个顶点的邻接顶点的入度数减一,并且把入度减少后度数为0的邻接顶点入队
            for(W = G->AL[V].FirstEdge; W; W = W->Next)
            {
                Indegree[W->V]--;
                if(Indegree[W->V] == 0)
                    AddQ(Q, W->V);
            }
        }
        //如果计数器计算的顶点小于原图中顶点数量,说明原图有回路,返回false
        if(cnt < G->VertexNum)
            return false;
        else
            return tru
        //结束算法
    }
    

7、关键路径计算

  • AOE网:即边表示活动的网。

    • AOE网是一个带权的有向无环图,其中顶点表示事件,弧表示活动,权值表示为活动持续的时间
    • AOE中入度为0的点称为源点出度为0的点称为汇点
    • 用AOE网可以表示一个工程,源点表示接到任务,汇点表示完成任务,完成某一个任务就到达某一个顶点,边表示完成该任务需要的时间。
  • ETV:表示事件最早发生的时间,也就是顶点最早发生时间。

  • LTV:事件最晚需要开始时间,如果超过了这个时间就会延误工期。

  • 关键路径:ETV和LTV相同的顶点是关键顶点,因为关键顶点不能延误,从源点到汇点和经过的关键顶点形成的路径就是关键路径

  • //关键路径的计算
    //用拓扑排序可求得顶点的ETV和LTV
    //这里用邻接表储存图
    
    //拓扑排序求ETV和LTV
    bool TopSort(LGraph G, int ETV[], int LTV[])
    {
    	//建立一个TopOrder数组,用来存排序好的顶点
    	Vertex TopOrder[G->VertexNum];
    	//遍历图,得到所有顶点的入度,并存入Indegree数组
        Vertex Indegree[G->VertexNum];
    	Vertex V;
    	AdjV W;
        for(V = 0; V < G->VertexNum; V++)
        	for(W = G->AL[V].FirstEdge; W == NULL; W = W->Next)
            {
                Indegree[W->V]++;
            }
    	//创建队列,并将所有入度为0的顶点入队,更新ETV数组
        Queue Q = CreateQueue(G->VertexNum);
        for(V = 0; V < G->VertexNum; V++)
        {
            if(Indegree[V] == 0)
            {    
    			AddQ(Q, V);	
    		}
        }
    	//核心代码
        //设置一个计数器来观察所有出过队的顶点数量
        int cnt = 0;
    	//while循环来实现拓扑排序
        while(!IsEmpty(Q))	//当队列为空时说明没有入度为0的顶点,退出循环
        {
            //出队一个顶点,把它存入TopOrder数组,并将计数器加一
            V = DeleteQ(Q);
            TopOrder[cnt] = V;
            cnt++;
    		//遍历顶点V的邻接顶点,如果V的开始时间加活动时间大于邻接顶点的开始时间,则替换邻接顶点时间
    		for(W = G->AL[V].FirstEdge; W; W = W->Next)
    		{
    			if(ETV[V] + W->Weight > ETV[W->V])
    				ETV[W->V] = ETV[V] + W->Weight;
    		}	
            //把这个顶点的邻接顶点的入度数减一,并且把入度减少后度数为0的邻接顶点入队
            for(W = G->AL[V].FirstEdge; W; W = W->Next)
            {
                Indegree[W->V]--;
                if(Indegree[W->V] == 0)
                    AddQ(Q, W->V);
            }
        }
    	//如果计数器计算的顶点小于原图中顶点数量,说明原图有回路,返回false
        if(cnt < G->VertexNum)
            return false;
    	//根据TopOrder数组从最后元素开始计算LTV到下标为0的元素
    	//初始化LTV数组,将汇点值赋予LTV数组
    	for(V = 0; V < G->VertexNum; V++)
    	{
    		LTV[V] = TopOrder[G->VertexNum - 1];
    	}
    	for(V = G->VertexNum - 2; V >= 0; V--)
    	{
    		//遍历顶点V的邻接顶点,如果邻接点最晚时间减活动时间小于V的最晚时间,则替换V顶点时间
    		for(W = G->AL[V].FirstEdge; W; W = W->Next)
    		{
    			if(LTV[W->V] - W->Weight < LTV[V])
    				LTV[V] = LTV[W->V] - W->Weight;
    		}
    	}
    	//算法结束
    	return true;
    }
    
    
    //求关键路径函数,将关键顶点存入CriticalV[]数组中
    bool CriticalPath(LGraph G, Vertex CriticalV[])
    {
    	//设置ETV数组存每个顶点最早开始时间
    	//设置LTV数组存每个顶点最晚需开始时间
    	int ETV[G->VertexNum] = {0};
    	int LTV[G->VertexNum];
    	//利用拓扑排序得到ETV和LTV
    	if(TopSort(G, ETV, LTV) == false)
    		return false; 
    	//将每个顶点的ETV和LTV比较,如果相等就放入CriticalV数组
    	Vertex V;
    	int cnt = 0;
    	for(V = 0; V < G->VertexNum; V++)
    	{
    		if(ETV[V] == LTV[V])
    		{
    			CriticalV[cnt] = V;
    			cnt++;
    		}
    	}
    	return true;
    }
    

8、应用实例

  • 六度空间理论:“你和任何一个陌生人之间所间隔的人不会超过六个”。

  • 六度分隔理论的论证:在人际关系网络图G中,任意两个顶点之间都有一条路径长度不超过6的路径。可以采用广度优先算法对图G进行6层遍历,统计这些路径长度不超过6的顶点数,计算这些顶点数与所有顶点数的比例

  • #define SIX 6
    //标记顶点是否被访问的数组
    int Visited[MaxVertexNum] = {0};
    
    //以S点出发,对图G进行6次广搜,返回路径顶点数
    int SDS_BFS(LGraph G, Vertex S)
    {
    	Vertex V;
    	AdjV W;
    	//创建空队列
    	Queue Q;
    	Q = CreateQueue(MaxVertexNum);
    	//设置两个计数器,Count计数顶点数,Level计数当前层数
    	int Count = 1, Level = 0;
    	//设置一个顶点Last表示每层最后的顶点,Tail表示每层待更新的层尾顶点
    	Vertex Last, Tail;
    	Last = S;
    	//将S放入空队列
    	AddQ(Q, S);
    	//标记S;
    	Visited[S] = 1;
    
    	//利用循环广搜六次
    	while(!IsEmpty(Q))
    	{
    		//弹出V
    		V = DeleteQ(Q);
    		//将V的邻接点加入队列
    		for(W = G->AL[V].FirstEdge; W; W = W->Next)
    		{
    			if(Visited[W->V] == 0)
    			{
    				Visited[W->V] = 1;
    				//入队
    				AddQ(Q, W->V);
    				//统计人数
    				Count++;
    				//更新层尾
    				Tail = W->V;
    			}
    		}
    		//如果顶点V是层尾顶点则层数加一
    		if(V == Last)
    		{
    			Level++;
    			//更新下一层层尾顶点
    			Last = Tail;
    		}
    		//如果到了六层,那么退出循环
    		if(Level == SIX)
    			break;
    	}
    	//删除队列
    	DestoryQueue(Q);
    	return Count;
    }
    
    //检验六度空间理论
    void Six_Degrees_of_Separation(LGraph G)
    {
    	Vertex V;
    	int Count;
    	for(V = 0; V < G->VertexNum; V++)
    	{
    		Count = SDS_BFS(G, V);
    		printf("%d, %.2lf%%\n", V, 100.0 *(double)Count/ (double)G->VertexNum);
    	}
    }
    

二、感想

这一章学得真久/(ㄒoㄒ)/~~

标签:dist,记录,int,VertexNum,Vertex,学习,邻接,顶点,数据结构
From: https://www.cnblogs.com/dragon-dai/p/17711536.html

相关文章

  • JAVA从小白到微服务学习路线
    JAVA基础教程开发环境搭建JAVA基础语法数据类型流程控制数组面向对象方法重载封装继承多态抽象类接口枚举常用类泛型集合泛型注解异常处理多线程IO流反射StreamAPILambda表达式计算机基础数据结构与算法数据结构与算法基础(青岛大学-王卓)数......
  • 新手入门ArkTS调用NATIVE库的学习笔记
    【本文正在参加2023「盲盒」+码有奖征文活动】,活动链接https://ost.51cto.com/posts/25284前言本来想这周跟着HarmonyOS官网的codelabs学习一下ArkTS下对Native库的调用,不料harmonyos官网直接把这个Codelabs课程下线了,不知以后还会不会上线。上周五还看的挺正常的,自己还加入......
  • 学了1个月机器学习的总结
    书实在是厚,看不下去,还是看视频容易接受。总结: 入门应该从如何把点拟合成一条线开始。 先从统计学里的方差开始,扩展最小二乘法,引出线性回归。然后是逻辑回归,引出机器学习核心——求代价函数最小值。进而引出正则、学习率、过拟合欠拟合、偏差方差、准确率召回率、训练集验证集测试......
  • js学习
    变量   使用var定义的变量,在最外层定义时,可以是使用window获取  使用let和const时,就不行,let和cont是从当前作用域中获取     实现一个const   数据类型  null、undefined、NaN、0、空字符串 会在转换成布尔值的时候转化为falsefor循环......
  • vb.net 开发 excel Addin 学习(5)---- 几个小问题
    在做 excelAddin开发的时候越到了几个小问题。总结一下。一,Addin无缘无故不加载。没有任何痕迹可查询。解决方法: 可能是Excel禁止了你的addin,也就是你的addin被列入了黑名单,如果真是这样,看一下下面的(有图示说明),或许可以解决问题。在Excel2003中,点击标题栏中的......
  • 处理数据库中重复记录的方法
    数据库中的重复记录 ,一般都有可能包含垃圾数据,我们必然要处理它。其实处理它无外乎:查询,标记,删除。处理的方法也很多的,用sql语句都可以处理。有时也可以借助临时表。但是无论知道几种方法都不重要,只要会做就行了。即使茴香豆的茴字有一百种写法。我们还只是这......
  • 算法问题记录
    1. 故障量值跳变问题   原因:发生丢帧,数据取走不及时导致缓冲区中数据越积累越多。     解决方法:赶快把数据取走......
  • 基于可视化的可解释深度学习模型研究综述--草稿版
    ps:近期组会整理了一篇论文综述,先记录在案。摘 要: 深度学习能目前广泛应用于各个领域内,比如:医疗、交通以及娱乐等领域。随着社会的计算机算力的迅速增长以及GPU等硬件的支持,催生了一系列人工智能应用,例如医疗诊断、自动驾驶和个性化推荐等。得益于这一系列应用,人类社会生产......
  • 怎样在触发器中删除刚刚录入但是不合法的记录?
    建立一个临时表:CREATEGLOBALTEMPORARYTABLEnorthsnow_tmp(northsnow_idvarchar2(20))ONCOMMITDELETEROWS;在业务表上创建一个行级触发器:createorreplacetriggertrg_northsnowafterinsertontb_northsnowforeachrow......
  • C# 学习
    C#学习基础知识.net两种交互模式:C/S:客户机Client/服务器Server模式B/S:浏览器Browser/服务器模式Server模式注释三种方法1.//2./**/3.///自动跳出文档注释快捷键快速对齐:Ctrl+k+d,若有语法错误,则无效(VB默认自动对齐,C#则没有)弹出智能提示:Ctrl+j折叠代码#r......