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

    0.PTA得分截图

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

    1.1 总结图内容

    • 定义:图是顶点和边的集合,存储多个点之间的关系,简单来说就是多对多的关系。
    • 分类:
      • 有向图:点之间的边有方向。边用尖括号"<>"表示。(术语:“弧”,表示有方向的边)
      • 无向图:点之间的边没有方向。边用圆括号"()"表示。
    • 术语:
      • 邻接点:不论是无向图还是有向图,边的两个顶点互为邻接点。
      • 度:无向图中,某点所连接的边叫做这个点的度;有向图中,以某点为边的起点的边数叫做这个点的出度,以某点为边的终点的边数叫做这个点的入度,入度和出度的和是这个点的度。
      • 度和边的关系:所有点的度之和/2=图的边数
      • 完全图:每两个点之间都有边(无向图是一条边,有向图是两点之间互相指向对方的两条边)。设某图有n个顶点,若是完全无向图,它有n(n-1)/2条边;若是完全有向图,它有n(n-1)条边。
      • 稠密图和稀疏图:稠密图就是接近于完全图的图;稀疏图就是边数远小于完全图边数的图。
      • 路径:一个点到另一个点所经过的顶点序列。
      • 路径长度:就是一条路径上所经过的边的数目。
      • 简单路径:除起始点和结束点可以相同,整条路径经过的点都不能相同。
      • 回路/环:就是起始点和结束点相同的路径。
      • 权:如果图的边是有数值的,这个数值就叫权,而这种图叫做带权图,也可以称作网。
    • 连通
      • 无向图的连通,就是某两点之间有路径,就可以说它们之间是连通的。如果在某个图中,任意两点都连通,这个图就可以叫做连通图,如果不是,则叫做非连通图。连通分量是无向图中极大的连通子图,简单来说,就是把一个图,分块,每块的点都可以最大限度地构成连通图。
      • 有向图中,如果任意两点都存在有向路径,则说明这个图是强连通图,如果不是,则这个图所有的强连通子图叫做它的强连通分量。找强连通分量,其实就是在找回路。
      • 连通图,最多有n(n-1)/2条边,最少有n-1条边。

    图存储结构

    邻接矩阵

    • 用二维数组来表示边。下标表示顶点,比如数组egde[i][j]表示的是i到j的边的情况,有边则数据不为0,无边则数据为0。注意,点到本身也是没有边的,即edge[i][i]=0.
    • 定义:
    #define MAXV 最大顶点个数
    typedef struct {
    	int number;/*顶点编号*/
    	/*顶点其他信息*/
    }VertexType;
    typedef struct {
    	int edges[MAXV][MAXV];/*邻接矩阵*/
    	int n, e;/*顶点数、边数*/
    	VertexType vexs[MAXV];/*顶点信息*/
    }MatGraph;
    

    一般可简化成:

    typedef struct  			
    {  int edges[MAXV][MAXV]; 	/*邻接矩阵*/
       int n,e;  			/*顶点数、弧数*/
    } MGraph;				
    
    • 比如:

      • 无向图:(有对称性)
      • 有向图:(不一定对称)
    • 特点:邻接矩阵是唯一的,它适用与稠密图的存储。空间复杂度为O(n²),其中n为顶点个数。

    邻接表

    • 结合数组和链表的方法来存储。每个顶点有一个单链表,连接这个顶点的所有邻接点,然后将这些顶点的单链表的头结点存到一个数组中。
    • 定义:
    typedef struct ANode
    {
    	int adjvex;			//该边的终点编号
    	struct ANode* nextarc;	//指向下一条边的指针
    	int info;	//该边的相关信息,如权重
    } ArcNode;				//边表节点类型
    typedef int Vertex;
    typedef struct Vnode
    {
    	Vertex data;			//顶点信息
    	ArcNode* firstarc;		//指向第一条边
    } VNode;				//邻接表头节点类型
    typedef VNode AdjList[MAXV];
    typedef struct
    {
    	AdjList adjlist;		//邻接表
    	int n, e;		//图中顶点数n和边数e
    } AdjGraph;
    
    • 比如:
      • 无向图:

      • 有向图:

    • 特点:邻接表不唯一,它比较适合稀疏图的存储,空间复杂度为O(n+e),其中n为顶点个数,e为边数。

    图遍历及应用

    DFS(深度优先遍历)

    • 就是选定一个点,然后遍历它的一个邻接点,再继续遍历这个邻接点的邻接点,以此类推。在遍历的过程中,还需要保证每个点只遍历一次,直到所有点都遍历过为止。
    • 比如:
    • 具体代码的实现:
    • 应用:
      • 判断是否连通:进行一次深度遍历,同时记录路径visited[顶点],如果是连通图,它的深度遍历就可以经过图中所有的点,如果不是连通图,那么它就无法遍历所有的顶点,visited数组会存在数值为0的点。
      • 具体代码:
    bool check(AdjGraph* G)
    {
    	int i;
    	bool flag = true;
    	for (i = 0; i < G->n; i++)/*初始化visited数组*/
    	{
    		visited[i] = 0;
    	}
    	DFS(G, 起始点);
    	for (i = 0; i < G->n; i++)
    	{
    		if (visited[i] == 0)/*出现为0的点说明不连通*/
    		{
    			flag = false;
    			break;
    		}
    	}
    	return flag;
    }
    
    - 找路径:从起始点开始遍历,到遇到终点的时候停止。方法:用一个数组来存路径,在原函数的基础上加上出口,即遇到终点时结束程序,输出存放路径的数组。代码实现如下:
    


    注:如果要找多条路径,则在函数末尾加上visted[u]=0.

    BFS(广度优先遍历)

    • 就是从某一点开始,访问它的所有邻接点,然后按它访问邻接点的顺序,接着访问它的邻接点的所有邻接点,每次遍历要考虑每个点只遍历一次。类似树结构的层次遍历。
    • 比如:
    • 具体的代码实现:
    • 最短路径:(类似之前的树结构学的迷宫)

    最小生成树相关算法及应用

    • 生成树是图中的一个最小连通子图,它的边为n-1,且不为回路。
    • 遍历产生的生成树:深度优先遍历得到的称为深度优先生成树,由广度优先遍历得到的生成树称作广度优先生成树。生成树不唯一(如下图所示)
    • 权值之和最小的称为最小生成树。

    普利姆算法(prim)

    • 思路:

      • 给定一个图,和一个起始点x。初始化U,使得U={x}。
      • 在x到其他顶点的边里,选择最小的那条边,并得到x的邻接点y,将y放入U中。
      • 判断y的加入是否产生了更短的路径,如果有更短的路径,修改候选边。
      • 重复2、3两步直到所有的点都在U中为止。
    • 具体代码:

    • 应用:pta 7-4 公路村村通
      主要代码如下:

    克鲁斯卡尔算法(Kruskal)

    • 思路:

      • 给定一个图,无需知晓起始点
      • 在所有边里找最小边,并将其标记,如果这个边没有让这个图产生回路,则可以把它加入生成树。
      • 重复第2步,直到生成树包含所有的点为止。
    • 具体代码:

      • 边的存储:
    typedef struct
    {
    	int u;/*边的起点*/
    	int v;/*边的终点*/
    	int w;/*边的权值*/
    }Edge;
    Edge E[MAXV];
    

    最短路径

    • 最短路径与最小生成树不同,它的路径上不一定有n个点。

    Dijkstra算法(迪杰斯特拉)

    • 思路:

      • 确定起始点x,存入S中;
      • 找x连着的边里最小的那条,得到邻接点y。
      • 把邻接点加入S中,同时判断它的加入是否会出现更短的路径,如果有更短的路径,则修改路径。
      • 重复2、3步直到S中包含所有顶点。
    • 具体代码:

    • 应用:pta 7-6 旅游规划

      • 主要代码:

    Floyd算法(弗洛伊德)

    • 思路:(找任意两点的最小路径)
      • 定义两个二维数组,A[i][j]表示顶点i到j的最短路径长度,path[i][j]表示对应路径的前继。
      • 考虑顶点x,思路有点像Dijkstra,修改数组A和path,(与这个点相关的路径长度不变,比如下标中有x的)
      • 重复考虑点直到所有点都遍历过。
      • 最后,根据数组path从终点找前继,直到起点,对应点就是路径的逆序列。数组A[起点编号][终点编号]就是所求的路径长度。
    • 时间复杂度:O(n³)
    • 具体代码:

    拓扑排序

    • 拓扑排序,是在一个有向无环图中找拓扑序列,要保证每个点只出现一次,且如果有A到B的一条路径存在,A必须排在B前面。也就是被指向的必须排在后面。
    • 思路:
      • 在图中选取一个没有入度的点,输出它,然后删掉这给顶点以及它所有的有向边;
      • 重复上述步骤,直到所有点都已经输出为止。
    • 具体代码:
    typedef struct/*头结点*/
    {
    	int data;/*顶点编号*/
    	int count;/*顶点的入度*/
    	ArcNode* firstarc;/*指第一条边*/
    }VNode; 
    

    • 注:如果要判断图是否有回路,可以在每次输出点的时候计数,如果最后输出点的个数与图中点的个数不同,则存在回路。原理是:拓扑排序输出的点的入度需要为0,如果存在回路,那么回路中的点的入度永远不可能为0,也就无法输出。

    关键路径

    • 关键路径是指有向图中从源点到汇点的最长路径。其中,关键路径中的边叫做关键活动。
    • 步骤:
      • 对图进行拓扑排序。
      • 在拓扑排序得到的序列的基础上,计算出边的最早开始时间和最晚开始时间,分别得到ve和vl数组。ve是当前点到起始点的最长路径,vl有点像是找当前点到终点的最长路径,然后用ve[终点]-最长路径;
      • 计算所有边的e和l,其中,对边,e=ve[i],l=vl[l]-边的权值
      • 如果e=l,那么它就是关键活动,而所有的关键活动相连,就是它的关键路径。

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

    • 学习了图结构,我认为它的应用其实还是比较贴近生活的。我们平常的课程表需要的排课、用到的导航,都与它有关。主要是要理解那些算法的方法,我虽然感觉我有点理解了,但是在具体提到它们的时候,可能还是有一点混乱,容易忘记那个算法具体是怎么做的,就只记得大致的思路。图结构的话,如果结合图来看,对算法的思路会更加明确。有时候看不懂算法的思路,可以去参考下网络上其他人的博客,他们有画各种不同的图和对应步骤的变化图,能够帮助我们理解算法的核心思想,我个人觉得还是很有用的。在预习的时候,在自己看的不是很明白的时候,就可以参考其他人的博客,帮助理解,如果在老师上课前就弄明白了算法的大致思路,上课的时候效率也比较高。如果单看代码,可能就没有那么直观明了。多看几个例子,我们理解的才能比较清楚。在写了一些代码后,我觉着需要注意的一点是,要看清题目对顶点的编号,注意它是从0开始编号还是从1开始编号,然后根据具体的要求建图(决定要不要用到0这个下标),再根据创建的图来写那些操作代码。还有就是,在写代码的时候,有些功能的实现需要用到辅助数组,而如果这个数组的大小过大,(它是在函数中的局部变量)就会导致堆溢出,程序就会报错,无法运行,而解决的办法是,把它直接定义成全局变量。

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

    2.1 题目及解题代码

    • 题目

    • 代码

    2.1.1 该题的设计思路

    • 分析:题目给了一个有向带权的图,需要解决两个问题:一是从给定的起始点到所有其他的点之间是否存在路径;二是需要求其他点到起始点的最短路径,并从这些最短路径中找到最大值。
    • 思路:用到的是求最短路径的Dijkstra算法
      • 从给定点k开始,选择与它最近的邻接点;
      • 判断这个邻接点的加入是否使得更短路径出现,如果存在更短的路径,则更新路径信息。
      • 重复上述过程,直到所有的点都遍历过为止。
      • 判断点k到其他点是否都有路径,如果不是输出-1,如果是,取这些最短路径的最大值。

    -时间复杂度:O(n²)空间复杂度:O(n²)

    2.1.2 该题的伪代码

    定义数组distance存放各点到起始点的最短路径长度,visited数组用来标记遍历过的点。
    
     读取time数组构建图的邻接矩阵
     for循环初始化distance
     for 1 to n
       for 1 to n+1
           找到未遍历过的且距离起始点最近的邻接点minIndex;
       end for
       for 1 to n+1
           判断与minIndex相连的边是否会使得各点到起始点的路径更短,如果是,更新distance数组
       end for
     end for
     for循环遍历数组distance
        if存在某个数据为-1,即某点与起始点之间无路径
           输出-1
        end if
        else 
            取distance的最大值
        end else
     end for
    
    

    2.1.3 运行结果

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

    • 优势:我们学过求最短路径的方法。
    • 难点:主要是对算法的熟悉和应用,之前求最短路径的时候,基本不存在没有路径的问题,但其实解决有没有路径的问题,就隐藏在算法中,对distance数组的处理,就包括了无路径的问题。

    2.2 题目及解题代码

    • 题目

    • 代码

    2.2.1 该题的设计思路

    • 思路:
      • 先用Floyd算法求出点与点之间的最短路径
      • 根据所求的最短路径,计算在允许的最大范围内每个点能够去到的城市的个数
      • 取城市个数最小的点(如果个数相同则取编号较大的那个点)
    • 时间复杂度:O(n³)空间复杂度:O(n²)

    2.2.2 该题的伪代码

    定义二维数组map存点与点之间的最短路径长度
     for循环初始化map数组
     for 0 to n(k)
        for 0 to n(i)
           for 0 to n(j)
              if i=j即不存在边
                  continue;
              end if
              else
              判断k点的存在是否会有更短的路径出现(map[i][j] > map[j][k] + map[i][k]),如果存在,则修改map值
              end else
           end for
        end for
     end for
    
     for n-1 to 0
        for 0 to n
            求在允许范围内的路径长度能到达的城市数量r
        end for
        比较min和r取最小值min并记录城市编号num
     end for
     return num;
    
    

    2.2.3 运行结果

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

    • 优势:我们学过最短路径的算法,而且这道题题目就很明确表示出,我们需要求最短路径。
    • 难点:选择什么方法来求最短路径,比如我们选的是Floyd算法,它的三层循环具体怎么操作。在规定长度内怎么算所能到达城市的数量。

    2.3 题目及解题代码

    • 题目:

    • 代码:

    2.3.1 该题的设计思路

    • 思路:题目中,法官是被其他人信任但不信任任何一个人的存在,在图中,就是出度为0入度为n-1的点。所以在邻接矩阵中,它所在的行对应的数据都为0(信任的人为0个),它所在的列,对应数据则都不是0.

    • 时间复杂度:O(n²) 空间复杂度:O(n²)

    2.3.2 该题的伪代码

     for循环读取trust数组赋值给邻接矩阵对应的位置
     for循环改邻接矩阵中自己对自己的信任为1
     for 1 to n(j)
        for 1 to n(i)
           如果这一列中数都为1则是法官,否则break
        end for
        if 这一列满足是法官的条件即i>n
           for 1 to n(k)
              如果这一行数都为0且本身为1则是法官,否则break
           end for
           if 在行上也满足条件即k>n
              保存这个点的编号为index
           end if
        end if
     end for  
     return index
    

    2.3.3 运行结果

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

    • 优势:这题如果用图来做,就是在考它的入度和出度的问题。还算是比较简单的。
    • 难点:如何判断一个人是法官是本题的重点,把法官问题转换为入度出度问题,这个思路上的转变很关键。如果没想到这一层,就会有点难做。
  • 相关阅读:
    爬取某APP的数据
    GPU操作
    Beyond过期处理方法
    tensorRT安装
    YOLOX multiGPU training
    linux服务器环境部署(三、docker部署nginx)
    linux服务器环境部署(二、docker部署jhipster-registry)
    linux服务器环境部署(一、docker部署)
    CAP原则
    热部署live-server
  • 原文地址:https://www.cnblogs.com/yubing----/p/12774883.html
Copyright © 2011-2022 走看看