zoukankan      html  css  js  c++  java
  • DS博客作业04--图

    0.PTA得分截图

    1.本周学习总结(0-5分)

    1.1 总结图内容

    • 定义:顶点集V和顶点间的关系:边集合E组成的数据结构

    • 基本术语

      ·无向图:没方向边(如图)

      ·有向图:由顶点集和弧集(有方向的边)构成的图(如图)

      ·完全图

      ·完全无向图:每两个顶点之间都存在这一条边

      ·完全有向图:每两个顶点之间都存在着方向相反的两条边

      ·稠密图:当一个图接近完全图时

      ·稀疏图:当一个图含有较少边数是

      ·度,入度,出度

      度:无向图以顶点i为端点的边数

      入度:以顶点i为终点的入边的数目

      出度:以顶点i为始点的出边的数目

      ·路径长度:一条路径上经过的边的数目

      ·简单路径:一条路径上除开始点和结束点可以相同外,其余顶点均不相同

      ·回路,环:一条路径上的开始点与结束点为同一个顶点

      ·简单回路,简单环:开始点与结束点相同的简单路径

      ·连通,连通图和连通分量

      ·连通图:图中任意两个顶点都相通

      ·连通分量:无向图G中的极大连通子图

      注:任何连通图的连通分量只有一个,为连通图本身;而非连通图由多个连通分量。

      ·权和网

      ·图中每一条边都可以附有一个对应的数值,即为权

      ·网:也称为带权图,即边上带有权的图。

    • 图的存储结构:

      邻接矩阵

      适用于稠密图

      存储类型定义如下:

      typedef struct 
      {    int no;	
           InfoType info;	
      } VertexType;
      typedef struct  		
      {    int edges[MAXV][MAXV]; 	
           int n,e;  		
           VertexType vexs[MAXV];	
      }  MatGraph;
       MatGraph g;
      
      

    注:图的邻接矩阵表示是唯一的

    邻接表

    对于图中的每一个顶点i建立一个单链表,将顶点i所有的邻接点都连起来

    是一种顺序分配和链式分配相结合的方法

    存储类型定义如下:

    typedef struct Vnode
    {    Vertex data;		
         ArcNode *firstarc;		
    }  VNode;
    //表明邻接表头结点类型
    typedef struct ANode
    {     int adjvex;		
          struct ANode *nextarc;
          InfoType info;	
    }  ArcNode;
    //表明边结点类型
    typedef struct 
    {     VNode adjlist[MAXV] ;	
           int n,e;		
    } AdjGraph;
    //声明图邻接表类型
    AdjGraph *G;//声明一个邻接表存储的图G
    
    

    • 图的遍历

    • 定义:从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次

    • 深度优先遍历

      ·实质:对每个顶点查找邻接点的过程

      ·伪代码:

      访问v节点
       遍历v的邻接点w
           若w未被访问,递归访问w节点
      
    • 广度优先遍历

      ·实质:利用队列,类似于层次遍历进行遍历

      ·伪代码

      建一个访问队列q
      访问v节点,加入队列q
      while(队列不空)
          取队头元素w
          遍历w的邻接表    
               取邻接点j
               若j未被访问,则加入队列q,并访问j。
      end while
      
    • 判断图是否连通

      ·利用深度优先遍历

      ·思路:给定一个visited[]数组并将其中所有元素初始化为0,从0开始进行DFS,结束遍历后若所有顶点i的visited[i]都为1 则说明图连通

      ·伪代码

      bool Connect(AdjGraph *G)
      {
          定义i;
          定义flag为true;
          for i=0 to G.n
              visited[i]=0;//赋初值
          调用DFS算法从0进行遍历;
          for i=0 to G.n
              if 顶点没有被访问到
                  flag=false;
          return true;  
      }
      
    • 查找图简单路径

      ·简单路径:即路径上的顶点不重复

      ·利用深度优先遍历

      ·思路:在DFS基础上加上v和has两个形参,has表示u到v是否有路径,若有,has赋值为true进行返回。

      ·伪代码:

      void ExistPath(AdjGraph *G,int u,int v,bool &has)
      {
          定义结点p;
          定义w;
          visited[u]=1;//置u已访问标记
          if u等于v  //找到一条路径
              has=true;
              return;
          DFS遍历;
      }
      
    • 找最短路径

      ·利用深度优先遍历

      ·思路:给定一个数组path[]存放路径,给定形参d了解遍历到了哪里即递归深度。利用DFS从顶点u遍历到顶点v,结束遍历后输出path。

      ·伪代码:

      void FindaPath(AdjGraph *G,int u,int v,int path[],int d)
      {
          定义结点p;
          定义w,i;
          d++;
          path[u]=1;//置u已访问标记
          if u等于v  //找到一条路径
              输出并返回;
          DFS遍历;
      }
      
    • 生成树和最小生成树

    • 生成树

    • 定义:一个连通图的生成树是一个极小连通子图,它含有图中全部n个顶点和构成一棵树的(n-1)条边,不能回路。

    • 广度优先生成树:由BFS遍历所得的生成树

    • 深度优先生成树:由DFS遍历所得的生成树

    • 最小生成树

    • 定义:图的所有生成树中具有边上的权值之和最小的数

    • 普里姆(Prim)算法

      ·一种增量算法,一步一步的选择最小边,并在U集合中添加相应的顶点。即利用贪心算法的原理,局部最优经过调整后成为全局最优,这种算法不需要回溯。

      ·图存储结构:邻接矩阵(需要频繁的取一条条的边)

      ·新建两个数组:closest[]和lowcost[],用于记录V-U中顶点j到U顶点的最小边(为节省空间)

      ·时间复杂度:O(n^2) 空间复杂度:O(n^2)

      ·伪代码:

      初始化lowcost,closest数组
      for(v=1;v<=n;v++)
          遍历lowcost数组     //选最小边
                 若lowcost[i]!=0,找最小边
                 找最小边对应邻接点k
          最小边lowcost[k]=0;
          输出边(closest[k],k);
          遍历lowcost数组     //修正lowcost
              若lowcost[i]!=0 && edges[i][k]<lowcost[k]
                      修正lowcost[k]=edges[i][k]
                       修正closest[j]=k;
      end
      
    • 克鲁斯卡尔(Kruskal)算法

      ·一种按权值的递增次序选择合适的边来构造最小生成树的方法

      ·图存储结构:邻接矩阵(频繁的取一条条边)

      ·新建一个数组:vest[]用于记录一个顶点i所在的连通分量编号

      ·时间复杂度:O(eloge) 空间复杂度:O(n)

      ·伪代码:

      void Kruskal(MatGraph g)
      {
          定义 i,j,u1,v1,sn1,sn2,k;
          定义辅助数组vest[];
          定义边结构体数组 E[];
          for i=0 to g.n
              for j=0 to g.n
                  对图中的边赋值;//起始顶点,终止顶点,权值
          采用直接插入排序对E按权值排序;
          初始化vest[];
          k=1;//表示当前构造生成树的第几条边
          j=0;//E边的下标
          while k<g.n
          {
              取一条边的两个顶点赋予u1,v1;
              得到两个顶点所属的集合编号于sn1,sn2;
              if sn1不等于sn2
              {
                  生成边数k++;
                  for i=0 to g.n
                      集合编号为sn2的改为sn1;
              }
              j++;
          }
      }
      

      ·改进克鲁斯卡尔(Kruskal)算法

      ·一:将边集排序改为堆排序 二:采用并查集进行连通分量合并

      ·改进后时间复杂度:O(log2 n)

      ·改进后的伪代码:

      void Kruskal(MatGraph g)
      {
          定义 i,j,u1,v1,sn1,sn2,k;
          定义并查集 t[];
          定义边结构体数组 E[];
         for i=0 to g.n
              for j=0 to g.n
                  对图中的边赋值;//起始顶点,终止顶点,权值
          采用堆排序对E数组按权值递增排序;
          初始化并查集;
          k=1;//表示当前构造生成树的第几条边
          j=0;//E边的下标
          while k<g.n
          {
              取一条边的两个顶点赋予u1,v1;
              得到两个顶点所属的集合编号于sn1,sn2;
              if sn1不等于sn2
              {
                  生成边数k++;
                  将u1和v1两个顶点合并;
              }
              j++;
          }
      }
      
    • 普里姆(Prim)算法&克鲁斯卡尔(Kruskal)算法

      ·当一个图有多个最小生成树时,两个算法的结果未必是相同的。

      ·Prim算法适用于稠密图,Kruskal算法适用于稀疏图

    • 应用

    • 公路村村通

      ·伪代码:

      nt prim(MG g, int& count)
      {
          定义数组lowcost[];
          定义数组closest[];
          定义总费用sumCost;
          for i=0 to g.n//赋初值
              让lowcost[i]=矩阵中第一行的权值//从1开始生成
              closest[i]=1;
          end for
          for i=1 to g.n
              min赋值极致大
              for j=1 to g.n
                  选出最小的成本赋值至min;
              将min累加至sumCount
              改变最小成本所在下标在lowcost[]中的值=0
              for j=1 to g.n
                  将lowcost[]和closest[]的值进行调整修改;
          end for
          return sumCost
      }
      
    • 最短路径

    • 从一个顶点到其余各顶点的最短路径

    • Dijkstra算法

      ·主要思想:

    ·图存储结构:邻接矩阵(需要频繁地取一条条边及其权值)

    ·数组dist[]:存放最短路径长度,数组path[]:最短路径序列的前一顶点的序号

    ·特点

    1、一个顶点一旦加入中,最短路径长度不再改变

    2、不适合含有负权值的带权图求单源最短路径

    ·伪代码:

    void Dijkstra(MatGraph g,int v)
    {
        初始化dist数组、path数组、s数组;
        遍历图中所有节点;
        for i=0 to g.n
            if s[i]!=0
                 则dist数组找最短路径,顶点为u;
        s[u]=1;  //加入集合S,顶点已选
        for i=0 to g.n
             if s[i]!=0 && dist[i]>dist[u]+g.edges[u][i]
                 则修正dist[i]= dist[i]>dist[u]+g.edges[u][i];
                 path[i]=u;      
    }
    
    • 每对顶点之间的最短路径

    • 每次以一个顶点为源点,重复执行Dijkstra算法n次

    • Floyd算法

      ·主要思想:递推产生一个矩阵序列A0,A1……,Ak[i][j]代表i->j的路径上所经过的顶点编号不大于k的最短路径长度。

    ·二维数组A:存储各顶点最短路径长度 , 二维数组path[]:存储最短路径

    ·伪代码:

    void Floyd(MatGraph g)
    {
        定义二维数组A[][],path[][];
        定义i,j,k;
        for i=0 to g.n
            for j=0 to g.n
                A[i][j]赋值;
                if i到j有边时
                    path[i][j]=i;
                else //若i到j无边
                    path[i][j]=-1;
        for k=0 to g.n
            for i=0 to g.n
                for j=0 to g.n
                    if A[i][j]>A[i][k]+A[k][j]
                        修改最短路径长度和最短路径;
    }
    
    • 应用

    • 旅游规划

      ·主要伪代码:

      void Dijkstra(MG g, int departure, int destination)
      {
          定义总路径长度,总收费金额;
          定义结构体数组d;
          定义数组path[],s[];
          for i=0 to g.n
              对结构体数组d进行赋值;
              对path[],s[]数组赋初值;
          end for 
          将出发顶点放入集合中;
          for i=1 to g.n
              min值赋为极大;
              for j=0 to g.n
                  比较获取长度最小的下标值k,并改变min值
              end for
          end for
          将k顶点放入集合中;
          for j=0 to g.n
              if 顶点不在集合中 then
                  if 路径长度相加是小于原本的路径长度 then
                      修改路径长度以及收费金额;
                  if 路径长度相等
                      只需修改收费金额;
          end for
          输出 目的地的路径长度和收费金额;
      }
      
    • 拓扑排序

      ·定义:在一个有向无环图中找一个拓扑序列的过程

      ·图存储结构:邻接表

      ·在定义邻接表结构体时可以加入顶点入度情况,从而遍历邻接表时可以计算每个顶点的入度

      ·多个入度为0的结点,入栈,之后进行出栈访问

      ·选择一个拓扑结点后,将其入度-1就可以实现连接边删除

      ·伪代码:

      void TopSort(AdjGraph *G)
      {
          遍历邻接表
            计算每个顶点的入度,存入头结点count成员;
          遍历图顶点
             if 顶点入度为0
                 入栈st;
          while(栈不空)
              出栈节点v,访问;
              遍历v的所有邻接点
              {
                  所有邻接点的入度-1  
                  if 邻接点入度为0
                      入栈st;
              }
      }
      
    • 关键路径

      ·定义:在AOE网中,从源点到汇点的所有路径中具有最大路径长度的路径

      ·事件的最早开始时间

    ·事件的最迟开始时间

    1.2.谈谈你对图的认识及学习体会。

    图的认识:
    这一章的内容
    1、对图相关概念的一个理解,图,有向图/无向图,度/入度/出度,完全图,子图,连通图……
    2、了解图的存储结构,邻接表/邻接矩阵
    3、图的基本运算,创建/销毁/输出……
    4、图的遍历,DFS/BFS
    5、生成树与最小生成树,Prim算法/Kruskal算法
    6、最短路径,Dijkstra算法/Floyd算法
    7、拓扑排序
    8、关键路径
    学习体会:
    总结下来,这一章有四个算法但在学习的时候我总觉得有十个算法围绕着我。。。有点难...有些东西是通过我这次的博客总结后重新认识和了解的。这一章节的pta,有一丢丢困难,困难也是疫情在家上网课懈怠了的原因。这一章节需要了解算法的内容需要,看到题目的时候才能合理的运用到他们身上,不然可能在创建图时是要邻接表还是邻接矩阵都在发愁……果然学习内容是需要总结,总结下来,头脑里面就会有一个框架也就不会那么乱。

    2.阅读代码(0--5分)

    2.1 判断二分图

    题目:

    代码:

    2.1.1 设计思路


    时间复杂度:O(n^3)
    空间复杂度:O(n^2)

    2.1.2 伪代码

    枚举color中元素 unknown 0,white 1,black 2;
    bool isBipartite(int **graph,int graphSize,int *graphColSize)
    {
        为pVisited申请空间;
        初始化pVisited中元素都为0;
        为pQueue申请空间;
        定义 front,rear,curr,cow,col,neighborColor;
        for row=0 to graphSize-1
        {
            if pVisited[row]!=0 //还未访问过
                continue;
            把row放入队列pQueue中;
            将pVisited[row]数值更改为1;
            while 队列不为空
            {
                将队头元素赋予curr;
                若pVisited[curr]为白,则改成黑色,若为黑,改为白色并赋予neighborColor;
                for col=0 to graphColSize[curr]-1 
                    if graph[curr][col]未被访问
                        将neighborColor赋予pVisited[graph[curr][col]];
                        graph[curr][col]入队;
                    if pVisited[graph[curr][col]] 与 neighborColor不同
                        return false;
            }
        }
    }
    

    2.1.3分析该题目解题优势及难点。

    解题优势:剖析本质为图染色问题,分别利用队列完成深度优先搜索,存储下一个要访问结点的顺序,先对每个未着色邻接点着色放入队列中。使用哈希数组记录每个结点的颜色,在搜索结点时如果存在当前点和邻接点颜色相反,则着色失败。
    难点:在于对这道题目的理解,如果没有较好的理解,也不会想到用图染色问题进行解决。还有是对哈希数组的利用,也很独到。

    2.2 阈值距离内邻居最少的城市

    题目:

    代码:

    2.2.1 设计思路

    2.2.2 伪代码

    int findTheCity(int n, vector <vector<int>> &edges, int distanceThreshold)
    {
        定义二维数组D,并初始化为INT_MAX(无穷);
        根据题目所给edges[][]初始化D[][];
        for k=0 to n
        {
            for i=0 to n
            {
                for j=0 to n
                {
                    if i等于j 或 矩阵中数值为INT_MAX
                        continue;//防止两个INT_MAX相加溢出;
                    D[i][j]等于(D[i][k]+D[k][j],D[i][j])中较小的那一个;
                }
            }
        }
        for i=0 to n
        {
            for j=0 to n
            {
                if i不等于j且路程小于distanceThreshold
                    cnt++;
            }
            if cnt小于等于minNum
                minNum=cnt;
                编号为i;
        }
        return 编号i;
    }
    

    2.2.3 运行结果

    2.2.4分析该题目解题优势及难点。

    解题优势:利用Floyd算法进行解题,最后再从小到大进行判断邻居最少的城市。就可以取较大编号邻居最少的城市了。
    难点:题目解读很重要,该代码利用邻接矩阵存图减少算法难度,利用Floyd算法计算便于查找邻居最少城市。

    2.3 节点间通路

    题目:

    代码:

    2.3.1 设计思路

    2.3.2 伪代码

    bool bfs(int n, vector<vector<int>>& edge, int start, int target)
    {
        定义队列que;
        定义标记访问数组vis,初始化false;
        start入队;
        更改vis[start]为true;
        while 队列不为空
        {
            取队头元素cur;
            删去队头;
            for i=0 to edge[cur].size()
                if edge[cur][i] 未被访问过
                    if edge[cur][i] 为目标终点
                        return true;
                    edge[cur][i]入队;
                    更改vis[edge[cur][i]] = true;
        }
        return false;
    }
    

    2.3.3 运行结果

    2.3.4分析该题目解题优势及难点。

    解题优势:利用队列,从0的链表访问能到达的节点入队。也利用了数组vis[],对已经入列的节点不再进行处理。
    难点:对于这个广度遍历的应用吧,其实这道题DFS和BFS都可以进行解题。

  • 相关阅读:
    [leetcode] Maximum Depth of Binary Tree
    [leetcode] Binary Tree Zigzag Level Order Traversal
    [leetcode] Binary Tree Level Order Traversal
    软工第二次极限测试
    动手动脑的问题以及课后实验性的问题3
    计算几何--半平面交与平面区域
    拉格朗日插值法
    计算几何--最小圆覆盖与最小球覆盖
    计算几何--圆与球
    程序员修炼之道读后感1
  • 原文地址:https://www.cnblogs.com/lz0149/p/12809084.html
Copyright © 2011-2022 走看看