• DS博客作业04--图


    0.PTA得分截图


    1.本周学习总结

    1.1 总结图内容


    图存储结构

    
    1.线性结构可以看成是树形结构的特殊情况。
    2.树形结构可以看成是图形结构的特殊情况。
    3.图形结构是最普遍的一类数据结构,具有广泛的实际应用。
    
    
    
      图的基本术语:
    
        1.端点和邻接点
          无向图中:若存在一条边(i,j),则称顶点i和顶点j互为邻接点。
          有向图中:存在一条边<i,j>,则称此边是顶点i的一条出边,同时也是顶点j的一条入边;称顶点i 和顶点j 互为邻接点。
    
        2.顶点的入度和出度:
          无向图中:以顶点i为端点,而它有几条边,就代表着有几个度
          有向图中:以顶点i为端点,有几条入边就是该顶点的入度,有几条边指向外边就代表出度,两个相加即为该顶点的总度
    
        3.稠密图和稀疏图
          当一个图接近完全图时,则称为稠密图。
          当一个图含有较少的边数时,则称为稀疏图。
    
        4.路径和路径长度
          路径长度是指一条路径上经过的边的数目。
          简单路径:一条路径上除开始点和结束点可以相同外,其余顶点均不相同
    
        5.回路和环
          回路或环:一条路径上的开始点与结束点为同一个顶点
          简单回路或简单环:开始点与结束点相同的简单路径。
    
        6.连通,连通图和连通分量
          无向图:若从顶点i到顶点j有路径,则称顶点i和j是连通
          连通图:若图中任意两个顶点都连通,否则称为非连通图
     
    

    邻接矩阵:

    1.顶点信息:记录各个顶点信息的顶点表。
    2.边或弧信息:各个顶点之间关系的邻接矩阵。

    
    #define  MAX
    typedef struct 
    {    int no;//顶点编号
         InfoType info;//顶点其他信息
    } VertexType;
    typedef struct 	//图的定义
    {    int edges[MAX][MAX]; //邻接矩阵
         int n,e;  	//顶点数,边数
         VertexType vexs[MAX];//存放顶点信息
    }  Graph;
     Graph g;//声明邻接矩阵存储的图
    
    

    主要特点:一个图的邻接矩阵表示是唯一的,特别适合于稠密图的存储,邻接矩阵的存储空间为O(n2)
    以下以图解来表示无向图和有向图关于邻接矩阵的用法:
    无向图:
    有向图:

    无向图邻接矩阵:

    有向图邻接矩阵:


    邻接表:

    存储方法:对图中每个顶点i建立一个单链表,将顶点i的所有邻接点链起来。
    每个单链表上添加一个表头结点。并将所有表头结点构成一个数组

    
    typedef struct node
    {    Vertex data;	//顶点信息
         ArcNode *firstarc;	//指向第一条边
    }  Node;
    
    typedef struct ANode
    {     int adjvex;		//该边的终点编号
          struct ANode *nextarc;	//指向下一条边的指针
          InfoType info;	//该边的权值等信息
    }  ArcNode;
    
    typedef struct 
    {     VNode adjlist[MAX] ;	//邻接表
           int n,e;	//图中顶点数n和边数e
    } AdjGraph;
    AdjGraph *G;//声明一个邻接表存储的图G
    
    

    承接邻接矩阵的图结构:

    无向图邻接表:

    有向图邻接表:


    图遍历及应用

    深度优先遍历(DFS):
    (1)从图中某个初始顶点v出发,首先访问初始顶点v。
     (2)选择一个与顶点v相邻且没被访问过的顶点w为初始顶点,再从w出发进行深度优先搜索,直到图中与当前顶点v邻接的所有顶点都被访问过为止。
    (3)深度优先遍历图的实质:对每个顶点查找其邻接点的过程
    (4)非连通图即多次调用DFS函数

    深度优先遍历算法思路:

    
    void DFS(ALGraph *G,int v)  
    {    ArcNode *p;
          visited[v]=1;                 
          printf("%d  ",v); 		
          p=G->adjlist[v].firstarc;      	
         while (p!=NULL) 
    	{
                      if (visited[p->adjvex]==0)  DFS(G,p->adjvex);    
    	     p=p->nextarc;              	
    	}
    }
    
    邻接表:时间复杂度即为O(n+e)
    邻接矩阵:O(n²)
    
    

    DFS:邻接表图解:

    广度优先搜索遍历(BFS):
    (1)访问初始点v,接着访问v的所有未被访问过的邻接点。
    (2)按照次序访问每一个顶点的所有未被访问过的邻接点。  
    (3)依次类推,直到图中所有顶点都被访问过为止。
    BFS类似于树的层次遍历:例子:

    该图的BFS序列为:2 1 3 4 0

    而他所对应的邻接表可以表示为:

    该算法的伪代码思路:

    
    建一个访问队列q
    访问v节点,加入队列q
    while(队列不空)
        取队头元素w
        遍历w的邻接表    
             取邻接点j
             若j未被访问,则加入队列q,并访问j。
    邻接表:时间复杂度为O(n+e)
    邻接矩阵:O(n2)
    
    

    遍历非连通图的算法:

    
    void  BFS(AdjGraph *G)
    {      int i;
            for (i=0;i<G->n;i++)     //遍历所有未访问过的顶点
                 if (visited[i]==0) 
                      BFS(G,i);
    }
    非连通图调用的BFS的函数的次数等于连通分量的个数
    
    

    如何判断图是否连通:
    判断图是否连通的想法很简单,就是从图中的任意一个顶点开始进行深度优先遍历搜索,如图中所有点都可以遍历到,即只有一个连通分量,则说明这个图连通;否则,该图不连通(当然选择的算法可以多种多样,这里说明DFS算法的判断过程)
    判断无向图是否连通的算法:

    
    int  visited[MAXV];//定义好数组
    bool Connect(AdjGraph *G) //判断无向图G的连通性
    {     int i;
          bool flag=true;//用flag来代替返回的true
          for (i=0;i<G->n;i++) //visited数组置初值
    	visited[i]=0;
          DFS(G,0); 	//调用前面的中DSF算法,从顶点0开始深度优先遍历
          for (i=0;i<G->n;i++)
                if (visited[i]==0)//判断
               {     flag=false;
    	   break;
               }
          return flag;
    }
    
    

    如何查找图路径:

    采用邻接表才存储图G,再进行查找
    查找图路径即查找出所有简单的路径
    算法思路:
    1.采用深度优先遍历的方法。
    2.增加path[i],存放路径。
    3.递归函数添加形参d,表示目前递归深度。
    4.当从顶点u遍历到顶点v后,输出path并返回。

    
    void FindAllPath(AGraph *G,int u,int v,int path[],int d)
    { 
      int w,i;  ArcNode *p;
      d++; path[d]=u;		//路径长度d增1,顶点u加入到路径中
      visited[u]=1;		//置已访问标记
      if (u==v && d>=1)		//找到一条路径则输出
            {	for (i=0;i<=d;i++)
    	    printf("%2d",path[i]);
    	printf("
    ");
            }
            p=G->adjlist[u].firstarc;	//p指向顶点u的第一个相邻点
            while (p!=NULL)
            {	 w=p->adjvex;		//w为顶点u的相邻顶点
    	 if (visited[w]==0)	//若w顶点未访问,递归访问它
    	     FindAllPath(G,w,v,path,d);
    	 p=p->nextarc;		//p指向顶点u的下一个相邻点
            }
           visited[u]=0;
    }
    
    注意:最后的visited[u]=0的目的是恢复环境,使该顶点可重新使用
    
    
    


    对于该图的最后表示1到4的所有路径有:
    1 2 4
    1 2 3 4
    1 0 3 4
    1 0 3 2 4

    如何找最短路径:

    首先我们先定义一个非循环队列类型:

    
    typedef struct
    {      int data;	//顶点编号
           int parent;	//前一个顶点的位置
    } QUERE;
    
    

    查找最短路径以路径上经过的边数来衡量路径长度;最短路径即路径上经过的顶点树最少的路径
    广度优先遍历找到的路径一定是最短路径,而深度优先遍历则不一定。
    深度优先遍历能找所有路径,而广度优先遍历难以实现
    算法如下:

    
    void ShortPath(AdjGraph *G,int u,int v)
    {   //输出从顶点u到顶点v的最短逆路径
           qu[rear].data=u;//第一个顶点u进队
            while (front!=rear)//队不空循环
            {      front++;		//出队顶点w
                   w=qu[front].data;
                  if (w==v)   根据parent关系输出路径break; 
                  while(遍历邻接表)   
                    {         rear++;//将w的未访问过的邻接点进队
    		 qu[rear].data=p->adjvex;
    		 qu[rear].parent=front;
    	  }
             }	      
    }
    
    
    

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

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

    该树的一颗生成树:

    生成树可以通过遍历的方法来产生生成树;DFS和BFS都是可以的,但是一个连通图的生成树是不一定唯一的

    最小生成树的概念:
    对于带权连通图G ,n个顶点,n-1条边
    根据深度遍历或广度遍历生成生成树,树不唯一
    其中权值之和最小的生成树称为图的最小生成树。
    非连通图:需要多次调用遍历的过程

    1.普里姆(Prim)算法

    普里姆(Prim)算法是一种构造性算法,用于构造最小生成树。
    过程如下:

    
    (1)初始化U={v}。v到其他顶点的所有边为候选边;
    (2)重复以下步骤n-1次,使得其他n-1个顶点被加入到U中:
            从候选边中挑选权值最小的边输出,设该边在V-U中的顶点是k,将k加入U中;
            考察当前V-U中的所有顶点j,修改候选边:若(j,k)的权值小于原来和顶点k关联的候选边,则用(k,j)取代后者作为候选边。
    
    

    普利姆算法设置了两个辅助数组来进行解题,用closest[i]来表示最小生成树的边依附在U中顶点编号;用lowcost[i]表示顶点i(i ∈ V-U)到U中顶点的边权重,取最小权重的顶点k加入U。并规定lowcost[k]=0表示这个顶点在U中

    例子:
    从顶点A出发,顶点BCD 到顶点 A 的权值分别为 2、4、2,

    分析顶点 C 和 D,顶点 C 到 B 的权值为 3,到 A 的权值为 4;顶点 D 到 A 的权值为 2,到 B 的权值为无穷大。所以顶点 D 到 A 的权值最小:

    只剩下顶点 C,到 A 的权值为 4,到 B 的权值和到 D 的权值一样大为3。所以该连通图有两个最小生成树

    算法如下:

    
    #define INF 32767		//INF表示∞
    void Prim(MGraph g,int v)
    {  int lowcost[MAXV],min,closest[MAXV],i,j,k;
       for (i=0;i<g.n;i++)	//给lowcost[]和closest[]置初值
       {  lowcost[i]=g.edges[v][i];closest[i]=v;}
        for (i=1;i<g.n;i++)	  //找出(n-1)个顶点
       {	min=INF;
    	for (j=0;j<g.n;j++) //     在(V-U)中找出离U最近的顶点k
    	   if (lowcost[j]!=0 && lowcost[j]<min)
    	   {	min=lowcost[j];  k=j;	/k记录最近顶点的编号}
    	   printf(" 边(%d,%d)权为:%d
    ",closest[k],k,min);
    	   lowcost[k]=0;		//标记k已经加入U
       for (j=0;j<g.n;j++)	//修改数组lowcost和closest
    	   if (lowcost[j]!=0 && g.edges[k][j]<lowcost[j])
    	   {	lowcost[j]=g.edges[k][j];
    		closest[j]=k;
    	   } 
      }
    }
    
    该算法的时间复杂度为O(n²)
    通过遍历lowcost数组来选出最小边;最后再进行重新遍历lowcost数组来进行最后的修正
    
    

    贪心算法:

    局部最优+最后调整=全局最优
    算法原理:以当前情况为基础作最优选择,而不考虑各种可能的整体情况,所以贪心法不要回溯。
    算法优点:因为省去了为寻找解而穷尽所有可能所必须耗费的大量时间,因此算法效率高。

    克鲁斯卡尔算法:

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

    
    (1)置U的初值等于V,TE的初值为空集。
    (2)将图G中的边按权值从小到大的顺序依次选取:
             若选取的边未使生成树T形成回路,则加入TE;
             否则舍弃,直到TE中包含(n-1)条边为止。
    
    TE表示最小生成树的边集;
    
    

    Kruskal算法描述:构造数组E来存储图中所有的边,该算法的时间复杂度为O(n²)

    定义类型:

    
    typedef struct 
    {    int u;     //边的起始顶点
         int v;      //边的终止顶点
         int w;     //边的权值
    } Edge; 
    
    
    

    算法如下:

    
    void Kruskal(AdjGraph *g)
    {     int i,j,u1,v1,sn1,sn2,k;
          int vset[MAXV]; //集合辅助数组
          Edge E[MaxSize];	//存放所有边
          k=0;			//E数组的下标从0开始计
        for (i=0;i<g.n;i++)	//由g产生的边集E,邻接表
    	{   p=g->adjlist[i].firstarc;
            while(p!=NULL)    
    	  {	E[k].u=i;E[k].v=p->adjvex;
                E[k].w=p->weight;
    		k++; p=p->nextarc;
    	  }
         }
         Sort(E,g.e);	//用快排对E数组按权值递增排序
          for (i=0;i<g.n;i++) 	//初始化集合
    	vset[i]=i;
            k=1;		//k表示当前构造生成树的第几条边,初值为1
    	j=0;		//E中边的下标,初值为0
    	while (k<g.n)	//生成的顶点数小于n时循环
    	{ 
                        u1=E[j].u;v1=E[j].v;	//取一条边的头尾顶点
    	      sn1=vset[u1];
    	      sn2=vset[v1];	//分别得到两个顶点所属的集合编号
     	      if (sn1!=sn2)  	//两顶点属于不同的集合
    	      {	printf("  (%d,%d):%d
    ",u1,v1,E[j].w);
    		k++;		   	//生成边数增1
    		for (i=0;i<g.n;i++)  	//两个集合统一编号
    		      if (vset[i]==sn2) 	//集合编号为sn2的改为sn1
    			vset[i]=sn1;
    	     }
    	     j++;			   //扫描下一条边
                }
    }
    
    
    

    最短路径相关算法及应用

    
      最短路径在交通问题中有着很大的作用,最短路径与最小生成树不同,路径上不一定包含n个顶点。最常用的解决最短路径的问题的两种算法:Dijkstra(迪杰斯特拉)算法和Floyd(弗洛伊德)算法
    ,Dijkstra(迪杰斯特拉)算法解决一顶点到其余各顶点,Floyd(弗洛伊德)算法解决任意两顶点之间
    
    

    Dijkstra算法:

    
    1.初始化:
      S={入选顶点集合,初值V0},T={未选顶点集合}。
      若存在<V0,Vi>,距离值为<V0,Vi>弧上的权值
      若不存在<V0,Vi>,距离值为无穷
      从T中选取一个其距离值为最小的顶点W, 加入S
    
    2.从T中选取一个其距离值为最小的顶点W, 加入S
    
    3..S中加入顶点w后,对T中顶点的距离值进行修改:
    若加进W作中间顶点,从V0到Vj的距离值比不加W的路径要短,则修改此距离值;
    
    4.重复上述步骤1,直到S中包含所有顶点,即S=V为止。
    
    

    该算法采用两个一维数组来辅助保存;用dist和path数组,path数组用于保存路径,dist用于保存权重;最短路径中所有顶点都是最短路径;若dist[k]+edge[k][j]<dist[j]则修正dist[j];

    该算法查找路径的图解和例子:

    1.从v0出发首先,到达最进的v1:

    2.1可以去往2,3,4,所以这个时候就看权重选择:

    3.接下来再继续看最短的权重走下去,2对应4是权重为1最小:

    4.以此类推:

    5.

    6.最后找到了0到8的最短路径

    代码实现如下:

    
    void Dijkstra(MatGraph g,int v)
    {     int dist[MAXV],path[MAXV];
          int s[MAXV];
          int mindis,i,j,u;
          for (i=0;i<g.n;i++)
          {       dist[i]=g.edges[v][i];	//距离初始化
    	s[i]=0;			//s[]置空
    	if (g.edges[v][i]<INF)	//路径初始化
    	       path[i]=v;		//顶点v到i有边时
    	else
    	      path[i]=-1;		//顶点v到i没边时
          }
          s[v]=1;	 		//源点v放入S中
           for (i=0;i<g.n;i++)	 	//循环n-1次
           {      mindis=INF;
    	for (j=0;j<g.n;j++)
    	     if (s[j]==0 && dist[j]<mindis) 
    	     {        u=j;
    		mindis=dist[j];
    	     }
    	s[u]=1;			//顶点u加入S中
    	for (j=0;j<g.n;j++)	//修改不在s中的顶点的距离
    	     if (s[j]==0)
    	          if (g.edges[u][j]<INF &&dist[u]+g.edges[u][j]<dist[j])
    	          {      dist[j]=dist[u]+g.edges[u][j];
    	   	   path[j]=u;
    	          }
          }
          Dispath(dist,path,s,g.n,v);	//输出最短路径
    }
    
    首先应该先实现dist和path数组的初始化,置访问过的顶点为1;循环n-1次将最小路径长度找到;最后在修改不在s中的顶点的举例,进行调整
    该算法时间复杂度为O(n²)
    
    

    Floyd算法:

    
    1.有向图G=(V,E)采用邻接矩阵存储
    2.二维数组A用于存放当前顶点之间的最短路径长度,分量A[i][j]表示当前顶点i到顶点j的最短路径长度。
    3.递推产生一个矩阵序列A0,A1,…,Ak,…,An-1
      Ak+1[i][j]表示从顶点i到顶点j的路径上所经过的顶点编号k+1的最短路径长度。
    
    

    现在画图举例:

    第一步:初始化矩阵S

    第二步:以顶点A为中介点,更新矩阵S:

    第三步:以顶点B为中介点,更新矩阵S:

    第四步:以顶点C为中介点,更新矩阵S:

    第五步:以顶点D为中介点,更新矩阵S:

    第六步:以顶点E为中介点,更新矩阵S:

    第七步:以顶点F为中介点,更新矩阵S:

    第八步:以顶点G为中介点,更新矩阵S:

    代码实现:

    
    void Floyd(MatGraph g)		//求每对顶点之间的最短路径
    {     int A[MAXVEX][MAXVEX];	//建立A数组
          int path[MAXVEX][MAXVEX];	//建立path数组
       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];
    	 if (i!=j && g.edges[i][j]<INF)
    	          path[i][j]=i; 	//i和j顶点之间有一条边时
         else			 //i和j顶点之间没有一条边时
    	          path[i][j]=-1;
         }
     for (k=0;k<g.n;k++)		//求Ak[i][j]
     {     for (i=0;i<g.n;i++)
           for (j=0;j<g.n;j++)
    	    if (A[i][j]>A[i][k]+A[k][j])	//找到更短路径
    	    {    A[i][j]=A[i][k]+A[k][j];	//修改路径长度
    	             path[i][j]=k; 	//修改经过顶点k
            }
       }
    }	
    
    

    拓扑排序、关键路径

    
    拓扑排序:在一个有向无环图中找一个拓扑序列的过程称为拓扑排序。 在一个有向图中找一个拓扑序列的过程称为拓扑排序。序列必须满足条件:
        每个顶点出现且只出现一次。
        若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
    图中如果有回路,就无法拓扑排序,拓扑排序可以用来检测图中是否有回路
    
    
    

    如何进行拓扑排序?
    1.从有向图中选取一个没有前驱的顶点,并输出之;
    2.从有向图中删去此顶点以及所有以它为尾的弧;
    3.重复上述两步,直至图空,或者图不空但找不到无前驱的顶点为止。

    头结点定义结构体:

    
    typedef struct 	       //表头节点类型
    {  vertex data;         //顶点信息
       int count;           //存放顶点入度
       ArcNode *firstarc;   //指向第一条弧
    } VNode;
    
    

    伪代码:

    
    遍历邻接表
          计算每个顶点的入度,存入头结点count成员
    遍历图顶点
        若发现入度为0顶点,入栈st
    while(栈不空)
    {
         出栈节点v,访问。
        遍历v的所有邻接点
           {     所有邻接点的入度-1  
                  若有邻接点入度为0,则入栈st
           }
    }
    
    
    

    关键路径:
    关键路径为源点到汇点的最长路径,这样转变为查找图中最长路径问题;
    求关键路径的步骤:

    
    1.对有向图拓扑排序
    2.根据拓扑序列计算事件(顶点)的ve,vl数组
              ve(j) = Max{ve(i) + dut(<i,j>)}
               vl(i) = Min{vl(j) - dut(<i,j>)}
    3.计算关键活动的e[],l[]。即边的最早、最迟时间
      e(i) = ve(j)
    	l(i) = vl(k) - dut(<j, k>
    4.找e=l边即为关键活动
    5.关键活动连接起来就是关键路径
    
    

    用ve(i)最早开始和vl(i)最迟开始;
    以此图为例子:

    蓝色代表最早开始,红色代表最迟开始:


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

    
    1.对于图的最初的存储结构的邻接矩阵和邻接表的理解要更深刻一点,因为这两种存储结构直接是编程的开始,图的存储结构分为邻接矩阵和邻接表,邻接矩阵适用于稠密图,邻接表适用于疏密图。对于这两种的存储结构用DFS和BFS的遍历比较了解。而对于其他的算法的步骤的理解比较困难,所以还有待去编程提升。
    2.关于DFS和BFS,有一些区别也需要去理解,比如对于搜索从一个顶点到另一个顶点的一条路径时,DFS求出的路径不一定时最短路径,而BFS求出的路径一定是最短路径。相对于不带权来说
    3.Prim算法和克鲁斯卡尔算法都是求最小生成树的算法,采用邻接矩阵最合适。克鲁斯卡尔算法更容易理解,但是Prim算法在代码上更容易实现。
    4.Dijkstra算法和Floyd算法,前者输出单源路径,后者输出多源路径。Dijkstra算法其实和Prim算法有很多相似,但需要修正的数据会比较多一些
    
    

    2.阅读代码

    2.1 题目及解题代码

    题目:

    解题代码:

    
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        List<Integer> ans = new ArrayList<>();
    
        if (n == 1) {
            ans.add(0);
            return ans;
        }
        int[] degree = new int[n];
        List<List<Integer>> map = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            map.add(new ArrayList<>());
        }
        for (int[] edge : edges) {
            degree[edge[0]]++;
            degree[edge[1]]++;
            map.get(edge[0]).add(edge[1]);
            map.get(edge[1]).add(edge[0]);
        }
    
        Queue<Integer> queue = new LinkedList<>();
    
    
        for (int i = 0; i < n; i++) {
            if (degree[i] == 1) {
                queue.offer(i);
            }
        }
     
        while (!queue.isEmpty()) {
            ans = new ArrayList<>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int cur = queue.poll();
                ans.add(cur);
                List<Integer> nexts = map.get(cur);
                for (Integer next : nexts) {
                    degree[next]--;
                    if (degree[next] == 1) {
                        queue.offer(next);
                    }
                }
            }
        }
    
        return  ans;
    }
    
    

    2.1.1 该题的设计思路

    根据示例2所给出的例子,可以得出下图:

    找出每个节点的一个度数;然后在找出每个节点链接的结点,度数为1入列,再将循环砍掉部分:

    以此类推:

    最后剩下的结点就是最小高度树;
    该题的时间复杂度为:O(n²);
    空间复杂度为O(n²)。

    2.1.2 该题的伪代码

    
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        List<Integer> ans = new ArrayList<>();
        
        if (节点个数为1) {
            ans.add(0);//不清楚
            return ans;
        }
        定义每个节点的度数;
        List<List<Integer>> map = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            map.add(new ArrayList<>());
        }
        for (int[] edge : edges) {
            计算edge[0]节点的度数;
            计算edge[1]节点的度数;
            记录跟edge[0]相邻的节点;
            记录跟edge[1]相邻的节点;
        }
        
        Queue<Integer> queue创建一个队列;
    
    
        for (int i = 0; i < n; i++) {
            if (度数为1,是叶子节点) {
                入队列;
            }
        }
        while (队列不为空) {
            给ans申请动态空间;
            定义 size = queue.size();
            for (int i = 0; i < size; i++) {
                List<Integer> nexts = map.get(cur);
                for (删除叶子节点) {
                    叶子节点其相邻的节点的度数要减少;
                    if (度数为1) {
                        queue.offer(next);
                    }
                }
            }
        }
    }
    
    
    

    2.1.3运行结果


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

    1.优势:该题的优势在于运用循环的办法和BFS的算法砍掉节点,利用degree数组,来存储每个节点的度数,又再次利用了队列的优势,是的题解的思路变得很清晰
    2.难点:该题的难点在于如何更好的利用队列来判断出在队列不为空的情况下将队列头部元素取出操作,还要将叶子删除节点后,节点的度数要-1.


    2.2题目及解题代码

    解题代码:

    
    class Solution {
    public:
        int maxDistance(vector<vector<int>>& grid) {
            int N = grid.size();
            int count_land = 0;
            int count_turn = 0;
            int count_last_space;
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (grid[i][j] == 1) {
                        count_land++;
                    }
                }
            }
            if (count_land == N * N || count_land == 0) {
                return -1;
            }
            count_last_space = N * N - count_land;
            while (count_last_space != 0) {
                count_turn++;
                for (int i = 0; i < N; i++) {
                    for (int j = 0; j < N; j++) {
                        if (grid[i][j] == count_turn) {
                            if (i > 0 && grid[i - 1][j] == 0) {
                                grid[i - 1][j] = count_turn + 1;
                                count_last_space--;
                            }
                            if (i < N - 1 && grid[i + 1][j] == 0) {
                                grid[i + 1][j] = count_turn + 1;
                                count_last_space--;
                            }
                            if (j > 0 && grid[i][j - 1] == 0) {
                                grid[i][j - 1] = count_turn + 1;
                                count_last_space--;
                            }
                            if (j < N - 1 && grid[i][j + 1] == 0) {
                                grid[i][j + 1] = count_turn + 1;
                                count_last_space--;
                            }
                        }
                    }
                }
            }
            return count_turn;
        }
    };
    
    
    

    2.2.1该题的设计思路


    (初始)

    对值为1的陆地判断上下左右,加一记录0
    以此类推:


    最后找到(4,4)
    空间复杂度为O(n²)
    时间复杂度O(n²)

    2.2.2该题伪代码

    
    class Solution {
    public:
        int maxDistance(vector<vector<int>>& grid) {
            int N = grid.size();
            int count_land = 0;
            int count_turn = 0;
            int count_last_space;
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (对值为1的陆地进行判断) {
             
                        进行“长大”,赋值为2;
                    }
                }
            }
    
            while (陆地值不全为0时) {
                count_turn值增加;
                for (int i = 0; i < N; i++) {
                    for (int j = 0; j < N; j++) {
                        if (该陆地的值和count_turn相等) {
                            if (该陆地的左边为0) {
                                该陆地的左边值+1
                                地图剩余0的个数-1;
                            }
                            if (该陆地的右边为0) {
                                该陆地的右边值 + 1
                                地图剩余0的个数 - 1;
                            }
                            if (该陆地值的上面为0) {
                                该陆地的上面的值 + 1;
                                地图剩余0的个数 - 1;
                            }
                            if (陆地下面方块值=0) {
                                陆地的下面方块+1;
                                地图剩余0的个数 - 1;
                            }
                        }
                    }
                }
            }
    
        }
    };
    
    
    

    2.2.3运行结果


    2.2.4该题的优势和难点

    1.优势:该题的优势利用了邻接矩阵一样的想法,利用表格的样子求解,记录了地图上0的个数,运行到直到没有0为止,在通过遍历来是的该赋值的区域进行加1,对于0的个数就减一,很好的理解
    2.难点:该题的难点在于:在图填满的时候,如何判断找到的值最大的领域,即最优海域,还要去最短的距离,就是该题的难点所在


    2.3题目及解题代码


    解题代码:

    
    class Solution {
        public int[] gardenNoAdj(int N, int[][] paths) {
    
            Map<Integer, Set<Integer>> graph = new HashMap<>();
            for (int i = 0; i < N; i++) {
                graph.put(i, new HashSet<>());
            }
    
            for (int[] path : paths) {
                int a = path[0] - 1;
                int b = path[1] - 1;
                graph.get(a).add(b);
                graph.get(b).add(a);
            }
            int[] res = new int[N];
            for (int i = 0; i < N; i++) {
                boolean[] used = new boolean[5];
      
                for (int adj : graph.get(i)) {
                    used[res[adj]] = true;
                }
    
                for (int j = 1; j <= 4; j++) {
                    if (!used[j]) {
                        res[i] = j;
                    }
                }
            }
            return res;
        }
    }
    
    

    2.3.1该题的设计思路

    
    
    1.首先给了每个节点提供四种颜色,度不超过3
    2.首先初始化结点,将结点及其邻结点的信息存储
    3.初始化图的路径的各个信息
    4.信息完善后,比那里所有结点,对于结点的邻接点进行判断,并且用不同的色进行染色
    5.该题的时间复杂度和空间复杂度均为O(n)
    
    

    2.3.2该题伪代码

    
    class Solution {
        public int[] gardenNoAdj(int N, int[][] paths) {
    
            Map<Integer, Set<Integer>> graph = new HashMap<>();
            for (int i = 0; i < N; i++) {
                初始化结点,用Map保存;
            }
    
            for (int[] path : paths) {
                初始化路径信息:;
                存储邻结点信息;
            }
            int[] res = new int[N];
            for (遍历所有结点) {
                boolean[] used = new boolean[5];
    
                for (int adj : graph.get(i)) {
                    查看当前结点的所有邻结点的色彩;
                    used[res[adj]] = true;
                }
                for (int j = 1; j <= 4; j++) {
                    if (如果邻接色不一样) {
                        为当前结点染色;
                    }
                }
            }
            end for;
            return res;
        }
    }
    
    

    2.3.3运行结果


    2.3.4优势和难点

    1.优势:该题思路很清晰,给每个结点度限制为3,只有四种颜色,所以这样不需要回溯,是的时间复杂度和空间复杂度变小。
    2.难点在于对于邻结点不同染色的不同判断,还需要对其进行查看,这是该题的主要难点。

  • 相关阅读:
    导入导出通用库
    镜像下载地址
    后端请求接口的几种方式
    获取本机的mac地址
    Arduino串口的一些高级用法
    手机与Arduino蓝牙串口通讯实验及完整例程
    ARDUINO+MCP2515实现CAN通讯接收
    Arduino IIC 主从设备连接通信
    上位机与单片机的通信问题
    NRF24L01多对一、多通道通讯关键代码
  • 原文地址:https://www.cnblogs.com/w-y-h--/p/12832475.html
Copyright © 2020-2023  润新知