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

    0.PTA得分截图

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

    1.1 总结图内容

    • 图存储结构

    图的存储结构主要分为邻接矩阵和邻接表存储。

    • 邻接矩阵
      顶点信息是记录各个顶点的信息顶点表,它是用来表示各个顶点之间关系的矩阵。设图A=(V,E)是一个有n个顶点的图,则关系为

      例如下面的一个无向图和一个有向图

      他们对应的邻接矩阵为

      可以看出无向图的邻接矩阵是关于对角线对称的,而有向图不一定是对称的
      如果顶点之间的边带权,还可以这样建矩阵

      对于不同的要求,还可以将i=j时的对角线A[i][j]置为0
      邻接矩阵的结构体定义一般如下
    typedef struct
    {
        int edges[MAX][MAX];//邻接矩阵
        int n,e;//顶点数,边数
    }MGraph;
    

    借助邻接矩阵求顶点的度很方便,对于无向图,第i行或第i列1的个数正好是顶点i的度;对于有向图,第i行1的个数正好是顶点i的出度,第i列1的个数正好是顶点i的入度。

    • 邻接表
      邻接表是一种顺序分配和链式分配相结合的存储方法。邻接表是由一个表头结点数组和n个以顶点为头结点的单链表组成的。
      表头结点和边结点的结构如图所示

      表头结点的data表示顶点信息;firstArc表示指向此单链表中的第一个邻接点。边结点的adjVex表示顶点代表的值;nextArc指向下一个边结点;value表示边信息,如权值等。
      在无向图中,顶点的度等于以该顶点为头结点的单链表中除了头结点的结点个数;在有向图中,顶点的出度等于以该顶点为头结点的单链表中除了头结点的结点个数,求入度时,需要遍历整个邻接表,统计某个边结点有多少个头结点。对于无向图来说,邻接表有n个表头结点和2e个边结点,对于有向图来说,邻接表有n个表头结点和e个边结点。
      邻接表的结构体定义一般为
    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;			//图中顶点数n和边数e
    } AdjGraph;
    AdjGraph *G;//声明一个邻接表存储的图G
    
    • 图遍历及应用。

    图的遍历是指从图中的某一个顶点出发,按照某种搜索方法访问图中的所有顶点,并且每个顶点都只能被访问一次。遍历的方法一般为两种,深度优先遍历(DFS),广度优先遍历(BFS)。

    • DFS深度优先搜索遍历
      过程一般为:(1)从图的某个顶点V出发,首先访问顶点V。(2)选择一个和顶点V相邻且没有访问过的顶点W,再从W出发进行深度搜索,直到顶点V的所有相邻顶点都被访问过。
      辅助数组visited[MAX]用来记录顶点是否已访问过。
    //邻接矩阵的深度优先遍历
    void DFS(MGraph &G, int v) 
    {
        int i;
        printf("%d", v);
        visited[v] = 1;
        for(i=0; i<G.vexNum; i++) 
        {
            if (G.arcs[v][i] != 0 && G.arcs[v][i] != INF&& !visited[i]) 
            {
                DFS(G, i);
            }
        }
    }
    //邻接表的深度优先遍历
    void DFS(ALGraph &G, int v)
    {
        ArcNode *p = G.adjList[v].firstArc;
        printf("%d", v);
        visited[v] = 1;
        while (p) 
        {
            if (!visited[p->adjvex]) 
            {
                DFS(G, p->adjvex);
            }
            p = p->nextarc;
        }
    }
    
    • BFS广度优先搜索遍历
      过程一般为:(1)访问初始顶点V,然后访问V的所有没有访问过的相邻顶点。(2)按照次序访问每个顶点的所有没有访问过的相邻顶点。(3)重复上述操作,直至所有顶点都被访问过。
      同样需要visited[]数组来判断顶点是否已访问。
    //邻接矩阵的广度优先遍历
    void BFS(MGraph G, Queue &Q, int v, int visited[]) 
    {
        int a, i = 0;
        enterQueue(Q, v);
        while(emptyQueue(Q)) 
        {
            a = deletQueue(Q);
            visited[a] = 1;
            printf("%d", G.vexs[a]);
            i=0;
            while(i<G.vexNum) 
            {
                if(G.arcs[a][i] == 1) 
                {
                    if(visited[i]==0) 
                    {
                        enterQueue(Q, i);
                        visited[i] = 1;
                    }
                }
                i++;
            }
        }
    }
    //邻接表的广度优先遍历
    void BFS(ALGraph G, Queue &Q, int v, int visited[]) 
    {
        int a;
        ArcNode *p=NULL;
        initQueue(Q);
        enterQueue(Q, v);
        
        visited[v] = 1;
        while (emptyQueue(Q)) 
        {
            a = deletQueue(Q);
            printf("%c	", G.adjList[a].vertex);
            p = G.adjList[a].firstArc;
            while (p) 
            {
                if(!visited[p->adjvex]) 
                {
                    enterQueue(Q, p->adjvex);
                    visited[p->adjvex] = 1;
                }
                p = p->next;
            }
        }
    }
    
    • 图的应用——如何判断图是否连通
      判断图是否连通,主要是判断visited[]数组的数组。采用某种遍历方法来遍历一个图,在一次遍历后,若所有顶点的visited[i]都为1,则图连通,否则不连通。
      下面是判断一个无向图是否连通。
    int  visited[MAXV];
    bool Connect(AdjGraph *G) 	//判断无向图G的连通性
    {     int i;
          bool 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;
    }
    
    • 图的应用——如何查找图路径
      深度遍历时将访问到的顶点的下标用path[]数组存储,若找到顶点i,则path数组中存储的就是顶点到i的一条路径;若未找到,则删除path中的一个下标然后返回,直至找到i。
    void DFSearchPath(MGraph G, int v, int w, int path[], int visited[], int &found) 
    {
        int j;
        Append(path, v);//存入path数组
        visited[v] = 1;
        for (j=0; j<G.vexNum && !found; j++) 
        {
            if (G.arcs[v][j]==1 && !visited[j]) 
            {
                if (j == w) 
                {
                    found=1;
                    Append(path, j);//存入path数组
                }
                DFSearchPath(G, j, w, path, visited, found);
            }
        }
        if (!found) 
        {
            Delete(path);//在path数组中删除
        }
    }
    
    • 图的应用——如何找最短路径
      要求最短路径,就要进行广度遍历,广度遍历找到的一条路径一定是最短的。
    void ShortPath(AdjGraph *G,int u,int v)
    {       //输出从顶点u到顶点v的最短逆路径
            qu[rear].data=u;//第一个顶点u进队
            while (队不空)
            {  
                  出队顶点w=qu[++front].data;
                  if (w==v)   根据parent关系输出路径break; 
                  while(遍历邻接表)   
                  {  rear++;//将w的未访问过的邻接点进队
    		 qu[rear].data=p->adjvex;
    		 qu[rear].parent=front;
    	      }
           }	      
    }
    
    • 最小生成树相关算法及应用

    最小生成树是指权值之和最小的生成树,如图所示

    求最小生成树一般有两种算法,一个是普里姆算法(Prim算法),一个是克鲁斯卡尔算法(Kruskal算法)。

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

      从顶点V0出发,到顶点V1、V5边的权值分别为3、4,因为到V1的权值更小,所以把V1加入到生成树中

      然后比较顶点V0和V1相邻的所有顶点边的权值,选择权值最小的V5加到生成树当中

      下面依此类推······

      最终找到一个最小生成树
      关于Prim算法代码的实现,要设置两个辅助数组。closest[i]数组来存放最小生成树的边依附在U中的顶点编号;lowcost[i]数组表示顶点i到U中顶点的边权值,取最小的权值的顶点K加入U,规定lowcost[K]=0表示顶点在U中。
    //利用Prim算法求最少消费
    int Prim(MGraph G, int a)
    {
    	int lowcost[MAX], closest[MAX];
    	int min, i, j, k;
    	int sum = 0;//为最少消费
    	for (i = 1; i <= G.n; i++)//初始化两个辅助数组
    	{
    		lowcost[i] = G.edges[a][i];
    		closest[i] = a;
    	}
    	for (i = 1; i <= G.n; i++)//初始化visited数组
    	{
    		visited[i] = 0;
    	}
    	visited[a] = 1;
    	for (i = 2; i <= G.n; i++)
    	{
    		min = INF;
    		for (j = 1; j <= G.n; j++)
    		{
    			if (lowcost[j] != 0 && lowcost[j] < min)
    			{
    				min = lowcost[j];
    				k = j;
    			}
    		}
    		sum = sum + min;
    		lowcost[k] = 0;
    		visited[k] = 1;
    		for (j = 1; j <= G.n; j++)
    		{
    			if (lowcost[j] != 0 && G.edges[k][j] < lowcost[j])
    			{
    				lowcost[j] = G.edges[k][j];
    				closest[j] = k;
    			}
    		}
    	}
    	for (i = 1; i <= G.n; i++)
    	{
    		if (visited[i]== 0)
    		{
    			return -1;
    		}
    	}
            
    	return sum;
    }
    
    • Kruskal算法
      克鲁斯卡尔算法是一种按权值的递增次序选择合适的边来构造最小生成树的方法,它过程一般为:(1)置U的初值等于V(即包含有G中的全部顶点),TE的初值为空集(即图T中每一个顶点都构成一个连通分量)。(2)将图G中的边按权值从小到大的顺序依次选取:若选取的边未使生成树T形成回路,则加入TE;否则舍弃,直到TE中包含(n-1)条边为止。
      下面举个例子

      选权值最小的边(V4,V7),如果没有构成环,则添加:

      选权值最小的边(V2,V8),如果没有构成环,则添加:

      选权值最小的边(V0,V1),如果没有构成环,则添加:

      下面依次类推······

      最终构成了一个完整的最小生成树
      在实现这个算法时,可以用一个数组E来存放图中的所有边,定义类型如下
    typedef struct 
    {    int u;     //边的起始顶点
         int v;      //边的终止顶点
         int w;     //边的权值
    } Edge; 
    Edge E[MAX];
    

    代码如下

    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算法
      1.初始化S={入选顶点集合,初值V0},U={未选顶点集合}。若存在<V0,Vi>,距离值为<V0,Vi>弧上的权值,若不存在<V0,Vi>,距离值为∞
      2.从U中选取一个其距离值为最小的顶点W, 加入S
      3.S中加入顶点w后,对U中顶点的距离值进行修改:
      若加进W作中间顶点,从V0到Vj的距离值比不加W的路径要短,则修改此距离值;
      4.重复上述步骤2,直到S中包含所有顶点,即S=V为止。
      这个算法用两个数组path[]和dist[]分别来存放最短路径和最短路径长度
      下面给一个例子


      关于Dijkstra算法的应用
    //求路径最小长度,及路径最少费用
    void Dijkstra(MGraph G, int s)
    {
    	int a[MAX];
    	int min, i, j, k;
    	for (i = 0; i < G.n; i++)
    	{
    		dist[i] = G.edges1[s][i];
    		money1[i] = G.edges2[s][i];
    		a[i] = 0;
    	}
    	a[s] = 1;
    	for (i = 0; i < G.n; i++)
    	{
    		min = INF;
    		for (j = 0; j < G.n; j++)
    		{
    			if (a[j] == 0 && dist[j] < min)
    			{
    				k = j;
    				min = dist[j];
    			}
    		}
    		a[k] = 1;
    		for (j = 0; j < G.n; j++)
    		{
    			if (a[j] == 0)
    			{
    				if (G.edges1[k][j] < INF && dist[k] + G.edges1[k][j] < dist[j])
    				{
    					dist[j] = dist[k] + G.edges1[k][j];
    					money1[j] = money1[k] + G.edges2[k][j];
    				}
    				else if (G.edges1[k][j] < INF && dist[k] + G.edges1[k][j] == dist[j])
    				{
    					if (money1[k] + G.edges2[k][j] < money1[j])
    					{
    						money1[j] = money1[k] + G.edges2[k][j];
    					}
    				}
    			}
    		}
    	}
    }
    
    • Floyd算法
      该算法用邻接矩阵存储,二维数组A存放两顶点之间的最短路径长度,A[i][j]表示从顶点i到顶点j的最短路径长度。思路为:(1)从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。(2)对于每一对顶点u和v,看看是否存在一个顶点w使得从u到w再到v比己知的路径更短。如果是更新它。
      代码如下
    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
             }
       }
    }	
    
    • 拓扑排序、关键路径

    • 拓扑排序
      在一个有向无环图中找一个拓扑序列的过程叫做拓扑排序,只有有向无环图才有拓扑序列,利用拓扑排序可以判断图中是否有回路。进行拓扑排序的方法一般为:1.从有向图中选一个没有前驱的顶点输出。2.从图中删掉这个顶点和所有以它为尾的弧。3.重复上述步骤,直到图为空,或者图不空但找不到没有前驱的顶点。
      头结点结构体:
    typedef struct
    {
        vertex data;
        int count;
        ArcNode *firstarc;
    }VNode;
    

    代码为

    //输出一个拓扑序列,若没有拓扑序列则输出error!
    void TopSort(AdjGraph* G)  //邻接表拓扑排序 
    {
    	int i, j;
    	int St[MAXV], top = -1;
    	int a[MAXV], k = 0;
    	ArcNode* p;
    	
    	for (i = 0; i < G->n; i++)		//入度置初值0
    	{
    		G->adjlist[i].count = 0;
    	}
    	for (i = 0; i < G->n; i++)		//求所有顶点的入度
    	{
    		p = G->adjlist[i].firstarc;
    		while (p != NULL)
    		{
    			G->adjlist[p->adjvex].count++;
    			p = p->nextarc;
    		}
    	}
    	for (i = 0; i < G->n; i++)		//将入度为0的顶点进栈
    	{
    		if (G->adjlist[i].count == 0)
    		{
    			top++;
    			St[top] = i;
    		}
    	}
    	while (top > -1)			//栈不空循环
    	{
    		i = St[top]; top--;			//出栈一个顶点i
    		a[k++] = i;
    		p = G->adjlist[i].firstarc;		//找第一个邻接点
    		while (p != NULL)		//将顶点i的出边邻接点的入度减1
    		{
    			j = p->adjvex;
    			G->adjlist[j].count--;
    			if (G->adjlist[j].count == 0)	//将入度为0的邻接点进栈
    			{
    				top++;
    				St[top] = j;
    			}
    			p = p->nextarc;		//找下一个邻接点
    		}
    	}
    	for (i = 0; i < G->n; i++)
    	{
    		if (G->adjlist[i].count != 0)
    		{
    			cout << "error!";
    			return;
    		}
    	}
    	for (i = 0; i < G->n; i++)
    	{
    		if (i == 0)
    		{
    			cout << a[i];
    		}
    		else
    		{
    			cout << " " << a[i];
    		}
    	}
    }
    
    • 关键路径
      在AOE网中,顶点表示事件,边表示活动,权表示活动持续时间,从源点到汇点的最长路径叫关键路径,源点是入度为0的顶点,汇点是出度为0的顶点,关键路径中的边叫关键活动。求关键路径也就是求图中的一条最长路径。
      求关键路径的步骤一般为: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.关键活动连接起来就是关键路径
      下面举个例子




      依此类推······




      取最小的Vl=0


      以此类推······

      当e=l时,为关键路径的边,图中有两条关键路径:a1,a4,a7,a10; a1,a4,a8,a11。

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

    通过对图这种多对多的关系结构的学习,我发现用它可以解决好多有趣的问题,找最短路径啊,找路费最少啊,还有怎么建网络啊,虽然代码有点难写,调试也十分的不方便,但它确实有点实际用处了。我知道了图有两种存储结构,分别为邻接矩阵和邻接表,图的遍历有深度优先遍历和广度优先遍历,可以用Prim算法和Kruskal算法求最小生成树,用Dijkstra算法求两个顶点的最短路径,用Floyd算法求所有顶点的最短路径,用拓扑排序来安排课表等问题,用关键路径来计划工程项目的完成顺序。

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

    2.1 题目及解题代码

    class Solution {
    public:
        int maxDistance(vector<vector<int>>& grid) {
            int n = grid.size();
            int dirs[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
            queue<pair<int, int>> que;
            
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (grid[i][j] == 1) {
                        que.push(make_pair(i, j));
                    }
                }
            }
            if (que.empty() || que.size() == n * n) return -1;
    
            pair<int, int> cur;
            while (!que.empty()) {
                cur = que.front();
                que.pop();
                for (int i = 0; i < 4; i++) {
                    int r_ = cur.first + dirs[i][0];
                    int c_ = cur.second + dirs[i][1];
                    if (r_ >= 0 && r_ < n && c_ >= 0 && c_ < n && grid[r_][c_] == 0) {
                        grid[r_][c_] = grid[cur.first][cur.second] + 1;
                        que.push(make_pair(r_, c_));
                    }
                }
            }
            return grid[cur.first][cur.second] - 1;
        }
    };
    

    2.1.1 该题的设计思路

    在一开始的时候将所有小岛都加入队列中,进行BFS遍历,队列中最后的那个位置的距离即为所求。

    时间复杂度和空间复杂度都是O(n*2)。

    2.1.2 该题的伪代码

    class Solution {
    public:
        int maxDistance(vector<vector<int>>& grid) {
            取网格大小n
            定义int dirs[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};来判断周围的海洋是否增加距离
            定义对了que
            
            for (int i = 0; i < n; i++) 
                for (int j = 0; j < n; j++)
                   把陆地进队que.push(make_pair(i, j));
            若队为空或者队的大小满了,则直接返回-1
            定义cur做中间变量
            while (队不空) {
                取队头cur = que.front();并出队que.pop();
                for (int i = 0; i < 4; i++) 
                    判断周围是否有海洋,若有距离加1,并进队
            }
            返回距离
        }
    };
    

    2.1.3 运行结果


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

    这道题是BFS算法问题,问题在于怎么使用BFS算法,这道题解的优势在于它从每个陆地开始向遍历,距离依次增加,遍历到最后,刚好可以得到最大的距离。

    2.2 题目及解题代码

    class Solution {
    public:
        vector<int> parents;
        vector<int> rank;
        int find(int x){
            if(x != parents[x]) 
                parents[x] = find(parents[x]);
            return parents[x]; 
        }
        void Union(int x,int y){
            if(rank[x] > rank[y]) parents[y] = x;
            else if(rank[y] > rank[x]) parents[x] = y;
            else{
                parents[x] = y;
                ++rank[y];
            }
        }
        vector<int> findRedundantConnection(vector<vector<int>>& edges) {
            int m = edges.size();
            parents = vector<int>(m+1);
            rank = vector<int>(m+1);
    
            for(auto edge : edges){
                parents[edge[0]] = edge[0],parents[edge[1]] = edge[1];
                rank[edge[0]] = rank[edge[1]] = 0;
            }
            for(vector<int> edge : edges){
                int x = find(edge[0]),y = find(edge[1]);
                if(x == y) return edge;
                else Union(x,y);
            }
            return {-1,-1};
        }
    };
    

    2.2.1 该题的设计思路

    首先初始化,每个节点之间都没有边,独自在各自的集合中,然后遍历每一条边:如果这条边的两个节点在同一个集合中,那么说明它们连接后将成环,因此是那条附加的边
    否则,将这两个节点连接起来,即放入同一个集合。
    空间复杂度和时间复杂度都是O(n)。

    2.2.2 该题的伪代码

    class Solution {
    public:
        定义两个全局变量,用于并查集的使用vector<int> parents;vector<int> rank;
        带路径压缩的查找int find(int x){
                          如果不是根节点,让父节点等于父节点的父节点
                               parents[x] = find(parents[x]);
                          返回父节点的值
                       }
    
        //按秩合并x,y所在的集合
        void Union(int x,int y){
            秩小的集合合并到秩大的集合
            秩相等时,随意将一个集合合并到另一个集合,并将合并之后的秩更新(加1)
                parents[x] = y;
                ++rank[y];
        }
        vector<int> findRedundantConnection(vector<vector<int>>& edges) {
            初始化parents 和 rank数组
                parents[edge[0]] = edge[0];parents[edge[1]] = edge[1];
                rank[edge[0]] = rank[edge[1]] = 0;
            判断每条边的两个顶点是否属于同一集合
        }
    };
    

    2.2.3 运行结果


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

    解题优势是巧妙的利用了并查集,只需找到已经连接的图中出现的第一条边,然后跟踪父结点,它会记录同一连接节点中较小结点的所在的集合。难点也在于并查集的实现,思路要清晰。

    2.3 题目及解题代码

    class Solution {
    public:
        int findTheCity(int n, vector <vector<int>> &edges, int distanceThreshold) {
            vector <vector<int>> D(n, vector<int>(n, INT_MAX));
            for (auto &e : edges) {
                D[e[0]][e[1]] = e[2];
                D[e[1]][e[0]] = e[2];
            }
            for (int k = 0; k < n; k++) {
                for (int i = 0; i < n; i++) {
                    for (int j = 0; j < n; j++) {
                        if (i == j || D[i][k] == INT_MAX || D[k][j] == INT_MAX) {
                           continue;
                        }
                        D[i][j] = min(D[i][k] + D[k][j], D[i][j]);
                    }
                }
            }
            int ret;
            int minNum = INT_MAX;
            for (int i = 0; i < n; i++) {
                int cnt = 0;
                for (int j = 0; j < n; j++) {
                    if (i != j && D[i][j] <= distanceThreshold) {
                        cnt++;
                    }
                }
                if (cnt <= minNum) {
                    minNum = cnt;
                    ret = i;
                }
            }
            return ret;
        }
    };
    

    2.3.1 该题的设计思路

    使用Floyd算法求出各个城市到其它城市的距离,保存在矩阵D[n][n]中。遍历D[n][n],统计各个城市在距离不超过 distanceThreshold 的情况下,能到达的其它城市的数量。最后返回能到达其它城市最少的城市 ret。
    时间复杂度O(n3),空间复杂度O(n2)。

    2.3.2 该题的伪代码

    class Solution {
    public:
        int findTheCity(int n, vector <vector<int>> &edges, int distanceThreshold) {
            定义二维D向量,并初始化各个城市间距离为INT_MAX(无穷)
            根据edges[][]初始化D[][]
                无向图两个城市间的两个方向距离相同
                D[e[0]][e[1]] = e[2];
                D[e[1]][e[0]] = e[2];
            // Floyd算法
            for (int k = 0; k < n; k++)
                // n个顶点依次作为插入点
                for (int i = 0; i < n; i++)
                    for (int j = 0; j < n; j++)//遍历各个顶点之间的距离,并用插入点进行更新
                        若i == j || D[i][k] == INT_MAX || D[k][j] == INT_MAX,则continue;
                        否则修改D[i][j] = min(D[i][k] + D[k][j], D[i][j]);
            选择出能到达其它城市最少的城市ret
            for (int i = 0; i < n; i++) 
                初始化cnt为0
                for (int j = 0; j < n; j++)
                    若i != j && D[i][j] <= distanceThreshold)
                        cnt++;
                若cnt <= minNum) 
                    minNum = cnt;
                    ret = i;
    };
    

    2.3.3 运行结果


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

    优势是利用的Floyd算法来来更改矩阵内容。

  • 相关阅读:
    将数据挂载到 docker 容器中的3种方式:volume、bind mount、tmpfs
    kubectl 常用命令
    Kubernetes 知识点
    spring boot 学习资料
    docker 常见系统镜像
    docker 的前台模式和后台模式
    Docker容器里的进程为什么要前台运行?相同的问题:docker运行apache为什么带FOREGROUND参数?docker运行nginx为什么带`daemon off`参数?
    spring cloud 各核心组件作用
    nginx 镜像使用说明
    optimization.splitChunks 中,chunks 的3个值:all、async、initial 的含义
  • 原文地址:https://www.cnblogs.com/wangtianxue/p/12832836.html
Copyright © 2011-2022 走看看