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

    0.PTA得分截图

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

    1.1 总结图内容

    图存储结构

    比较常见和重要的图的存储结构:邻接矩阵和邻接表。

    邻接矩阵:

    内容:

    邻接矩阵,顾名思义,是一个矩阵,一个存储着边的信息的矩阵,而顶点则用矩阵的下标表示。对于一个邻接矩阵M,如果M(i,j)=1,则说明顶点i和顶点j之间存在一条边,对于无向图而言,M(i,j)=M(j,i),所以其邻接矩阵是一个对称的矩阵;对于有向图而言,则未必是一个对称的矩阵。邻接矩阵的对角线元素都为0.如下图是一个无向图与其对应的邻接矩阵:

    注意点:

    图的邻接矩阵存储方式,结构由顶点数量、边数量、顶点集合和边集合组成。
    其中顶点集合一维数组,根据顶点的数量动态分配数组大小。
    边集合是二维数组,根据顶点的数量来动态分配数组大小,对于无向图来说,该邻接矩阵是对称矩阵。
    邻接矩阵比较适用于稠密图。

    结构体定义:
    #define  MAXV  <最大顶点个数>	
    typedef struct 
    {    int no;			//顶点编号
         InfoType info;		//顶点其他信息
    } VertexType;
    typedef struct  			//图的定义
    {    int edges[MAXV][MAXV]; 	//邻接矩阵
         int n,e;  			//顶点数,边数
         VertexType vexs[MAXV];	//存放顶点信息
    }  MatGraph;
     MatGraph g;//声明邻接矩阵存储的图
    
    应用:

    6-1 jmu-ds-邻接矩阵实现图的操作集

    邻接表:

    对于顶点数很多但是边数很少的图来说,用邻接矩阵显得略为“奢侈”,因为矩阵元素为1的很少,即其中的有用信息很少,但却占了很大的空间。所以邻接表的存在就很有必要了。

    内容:

    邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。
    对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。
    沿用上面邻接矩阵的无向图来构建邻接表,如下图所示:

    作为顶点0,它的邻接顶点有1,3,4,形成的边有(0,1),(0,3)和(0,4),所以顶点0将其指出来了;
    对于顶点1,它的邻接顶点有0,2,4,所以顶点1将其指出来了,以此类推。
    他们的边没有先后顺序之分。
    左边的节点称为顶点节点,其结构体包含顶点元素和指向第一条边的指针;
    右边的为边节点,结构体包含边的顶点对应的下标,和指向下一个边节点的指针。
    对于有权值的网图,只需要在边节点增加一个权值的成员变量即可。

    结构体定义:
    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
    
    应用:

    6-2 jmu-ds-图邻接表操作

    图遍历及应用。

    图的遍历算法有两种:深度优先搜索和广度优先搜索

    深度优先搜索:

    内容:

    深度优先搜索算法所遵循的策略是尽可能“深”地搜索一个图,它的基本思想是首先访问图中某一个起始定点v,然后由v出发,访问与v邻接且为被访问的任一个顶点w,再访问与w邻接且未被访问的任一顶点...重复上述过程,当不能再继续向下访问时,一次退回到最近被访问的顶点,若它还有邻接顶点未被访问,则从该点开始继续上述搜索过程,直到图中所有顶点均被访问过为止

    算法实现:
    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(n2)

    应用:

    7-1 图着色问题
    7-2 六度空间

    判断图是否连通:
    思想:

    采用某种遍历方式来判断无向图G是否连通。先给visited[]数组(为全局变量)置初值0,然后从0顶点开始遍历该图。
    在一次遍历之后,若所有顶点i的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;
    }
    
    应用:

    7-5 通信网络设计 (10分)

    查找图路径

    如图

    从图中的v1找到到v4的所有路径:
    1.从v1出发,将v1标记,并将其入栈。
    2.找到v0,将其标记,将其入栈。
    3.找到v4,将其标记,入栈。v4是终点,将栈中的元素从栈底往栈顶输出,即为一条路径。v4出栈,并取消标记,回溯到v0。
    4.v0除v4外无其它出度,将v0出栈,并取消标记,回溯到v1。
    5.找到v2,将其标记并入栈。
    6.找到v0,将其标记并入栈。
    7.找到v4,将其标记,入栈。v4是终点,将栈中的元素从栈底往栈顶输出,即为一条路径。v4出栈,并取消标记,回溯到v0。
    8.v0除v4外无其它出度,将v0出栈,并取消标记,回溯到v2。
    9.找到v3,将其标记并入栈。
    10.找到v4,将其标记,入栈。v4是终点,将栈中的元素从栈底往栈顶输出,即为一条路径。v4出栈,并取消标记,回溯到v3。
    11.v3除v4外无其它出度,将v3出栈,并取消标记,回溯到v2。
    12.v2除v0,v3外无其它出度,将v2出栈,并取消标记,回溯到v1。
    13.v1除v0,v2外无其它出度,将v1出栈,栈空,结束遍历。

    代码实现
    void DFS(int start,int end)//深搜入栈查询所有路径
    {
    	visited[start] = true;//visited数组存储各定点的遍历情况,true为已遍历(标记)
    	stack.Push(某个顶点);//入栈
    	for (int j = 0; j < list.Size(); j++) {
    		if (start== end) {//找到终点
    			for (int i=0; i < stack.Size()-1; i++) {
    				//输出从栈底到栈顶的元素,即为一条路径
    			}
    			stack.Pop();//出栈
    			visited[start] = false;
    			break;
    		}
    		if (!visited[j]) {//该顶点未被访问过
    			DFS(j,end);
    		}
    		if (j == list.Size() - 1 ) {//如果该顶点无其它出度
    		    stack.Pop();
    		    visited[start] = false;
    		}
    	}
    }
    

    广度优先搜索:

    宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
    通俗来说,从某点开始,走四面可以走的路,然后在从这些路,在找可以走的路,直到最先找到符合条件的,这个运用需要用到队列。

    代码实现:

    邻接矩阵:

    void BFS(AMGraph &G,char v0)
    {
    	int u,i,v,w;
    	v = LocateVex(G,v0);                            //找到v0对应的下标
    	printf("%c ", v0);                              //打印v0
    	visited[v] = 1;		                        //顶点v0已被访问
    	q.push(v0);			                //将v0入队
     
    	while (!q.empty())
    	{
    		u = q.front();				//将队头元素u出队,开始访问u的所有邻接点
    		v = LocateVex(G, u);			//得到顶点u的对应下标
    		q.pop();				//将顶点u出队
    		for (i = 0; i < G.vexnum; i++)
    		{
    			w = G.vexs[i];
    			if (G.arcs[v][i] && !visited[i])//顶点u和w间有边,且顶点w未被访问
    			{
    				printf("%c ", w);	//打印顶点w
    				q.push(w);		//将顶点w入队
    				visited[i] = 1;		//顶点w已被访问
    			}
    		}
    	}
    }
    

    邻接表:

    void BFS(ALGraph &G, char v0)
    {
    	int u,w,v;
    	ArcNode *p;
    	printf("%c ", v0);		                                        //打印顶点v0
    	v = LocateVex(G, v0);	                                                //找到v0对应的下标
    	visited[v] = 1;			                                        //顶点v0已被访问
    	q.push(v0);				                                //将顶点v0入队
    	while (!q.empty())
    	{
    		u = q.front();		                                        //将顶点元素u出队,开始访问u的所有邻接点
    		v = LocateVex(G, u);                                            //得到顶点u的对应下标
    		q.pop();			//将顶点u出队
    		for (p = G.vertices[v].firstarc; p; p = p->nextarc)		//遍历顶点u的邻接点
    		{
    			w = p->adjvex;	
    			if (!visited[w])	//顶点p未被访问
    			{
    				printf("%c ", G.vertices[w].data);	        //打印顶点p
    				visited[w] = 1;				        //顶点p已被访问
    				q.push(G.vertices[w].data);			//将顶点p入队
    			}
    		}
    	}
    }
    

    查找最短路径

    有Dijkstra(迪杰斯特拉)算法和用Floyd(弗洛伊德)算法

    Dijkstra(迪杰斯特拉)算法:

    迪杰斯特拉(Dijkstra)算法主要是针对没有负值的有向图,求解其中的单一起点到其他顶点的最短路径算法。

    算法思想:

    设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。

    具体操作

    1.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。
    2.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
    3.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
    4.重复步骤b和c直到所有顶点都包含在S中。

    代码实现(仅仅只是思路,未进行测试)
    const int  MAXINT = 32767;
    const int MAXNUM = 10;
    int dist[MAXNUM];
    int prev[MAXNUM];
    int A[MAXUNM][MAXNUM];
    void Dijkstra(int v0)
    {
        bool S[MAXNUM];                                  // 判断是否已存入该点到S集合中
          int n=MAXNUM;
        for(int i=1; i<=n; ++i)
        {
            dist[i] = A[v0][i];
            S[i] = false;                                // 初始都未用过该点
            if(dist[i] == MAXINT)    
                  prev[i] = -1;
            else 
                  prev[i] = v0;
         }
         dist[v0] = 0;
         S[v0] = true;   
        for(int i=2; i<=n; i++)
        {
             int mindist = MAXINT;
             int u = v0;                               // 找出当前未使用的点j的dist[j]最小值
             for(int j=1; j<=n; ++j)
                if((!S[j]) && dist[j]<mindist)
                {
                      u = j;                             // u保存当前邻接点中距离最小的点的号码 
                      mindist = dist[j];
                }
             S[u] = true; 
             for(int j=1; j<=n; j++)
                 if((!S[j]) && A[u][j]<MAXINT)
                 {
                     if(dist[u] + A[u][j] < dist[j])     //在通过新加入的u点路径找到离v0点更短的路径  
                     {
                         dist[j] = dist[u] + A[u][j];    //更新dist 
                         prev[j] = u;                    //记录前驱顶点 
                      }
                  }
         }
    }
    

    Floyd(弗洛伊德)算法:

    是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题。时间复杂度为O(N3),空间复杂度为O(N2)。

    算法思想:

    从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。

    具体操作:

    1.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。   
    2.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。

    代码实现:
    typedef struct          
    {        
        char vertex[VertexNum];                                //顶点表         
        int edges[VertexNum][VertexNum];                       //邻接矩阵,可看做边表         
        int n,e;                                               //图中当前的顶点数和边数         
    }MGraph; 
    void Floyd(MGraph g)
    {
       int A[MAXV][MAXV];
       int path[MAXV][MAXV];
       int i,j,k,n=g.n;
       for(i=0;i<n;i++)
          for(j=0;j<n;j++)
          {   
                 A[i][j]=g.edges[i][j];
                path[i][j]=-1;
           }
       for(k=0;k<n;k++)
       { 
            for(i=0;i<n;i++)
               for(j=0;j<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;
                    } 
         } 
    }
    

    应用:

    7-6 旅游规划
    6-4 jmu-ds-最短路径

    最小生成树:

    prim算法:

    普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。

    具体操作:

    输入:一个加权连通图,其中顶点集合为V,边集合为E;
    初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
    重复下列操作,直到Vnew = V:
    在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
    将v加入集合Vnew中,将<u, v>边加入集合Enew中;
    输出:使用集合Vnew和Enew来描述所得到的最小生成树。

    代码实现:
    void prim()
    {
        for (int i=1;i<=n;i++)    //以1作为根节点
        {
            if(edge[1][i]!=INF)
                p[i]=1;
            d[i]=edge[1][i];
        }
        while (1)
        {
            int maxx=INF;
            int u=-1;
            for (int i=1;i<=n;i++)        //找到距离生成树最短距离的节点
            {
                if(!vis[i]&&d[i]<maxx)
                {
                    maxx=d[i];
                    u=i;
                }
            }
            if(u==-1)
                break;
            vis[u]=1;
            for (int i=1;i<=n;i++)
            {
                if(!vis[i]&&d[i]>edge[u][i])
                {
                    d[i]=edge[u][i];
                    p[i]=u;
                }
            }
        }
        int sum=0;
        for (int i=1;i<=n;i++)
            if(p[i]==-1)
            {
                printf("不存在最小生成树
    ");
                return ;
            }
            else
                sum+=d[i];
            printf("最短生成树的长度为%d
    ",sum);
            return ;
    }
    
    时间复杂度:

    邻接矩阵:O(n2)
    邻接表:O(elog2n)

    Kruskal算法

    克鲁斯卡尔算法的基本思想是以边为主导地位,始终选择当前可用的最小边权的边。每次选择边权最小的边链接两个端点是kruskal的规则,并实时判断两个点之间有没有间接联通。

    具体操作:

    输入:图G
    输出:图G的最小生成树
    (1)将图G看做一个森林,每个顶点为一棵独立的树
    (2)将所有的边加入集合S,即一开始S = E
    (3)从S中拿出一条最短的边(u,v),如果(u,v)不在同一棵树内,则连接u,v合并这两棵树,同时将(u,v)加入生成树的边集E'
    (4)重复(3)直到所有点属于同一棵树,边集E'就是一棵最小生成树

    代码实现:
    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;
    }
    

    应用:

    7-4 公路村村通

    拓扑排序:

    介绍:

    对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行(对于数据流来说就是死循环)。在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列,由AOV网构造拓扑序列的过程叫做拓扑排序。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。

    实现步骤:

    在有向图中选一个没有前驱的顶点并且输出;
    从图中删除该顶点和所有以它为尾的弧,即删除所有和它有关的边;
    重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。

    举例:

    如下图:

    首先,我们发现V6和v1是没有前驱的,所以我们就随机选去一个输出,我们先输出V6,删除和V6有关的边,得到下图:

    然后,我们继续寻找没有前驱的顶点,发现V1没有前驱,所以输出V1,删除和V1有关的边,得到下图:

    再然后,我们又发现V4和V3都是没有前驱的,那么我们就随机选取一个顶点输出,我们输出V4,得到下图:

    再再再然后,我们输出没有前驱的顶点V3,得:

    没有然后了,最后我们分别输出V5和V2,最后全部顶点输出完成,
    该图的一个拓扑序列为:v6–>v1—->v4—>v3—>v5—>v2。

    结构体定义:

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

    代码实现:

    void TopSort(AdjGraph *G)	//拓扑排序算法
    {      int i,j;
            int St[MAXV],top=-1;	//栈St的指针为top
            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;
    	}
    }
    

    应用:

    6-3 jmu-ds-拓扑排序 (20分)

    关键路径:

    介绍:

    关键路径:AOE-网中,从起点到终点最长的路径的长度(长度指的是路径上边的权重和)。
    假设起点是v0,则我们称从v0到vi的最长路径的长度为vi的最早发生时间,同时,vi的最早发生时间也是所有以vi为尾的弧所表示的活动的最早开始时间,使用e(i)表示活动ai最早发生时间,除此之外,我们还定义了一个活动最迟发生时间,使用l(i)表示,不推迟工期的最晚开工时间。我们把e(i)=l(i)的活动ai称为关键活动,因此,这个条件就是我们求一个AOE-网的关键路径的关键所在了。

    具体步骤:

    1.输入顶点数和边数,已经各个弧的信息建立图
    2.从源点v1出发,令ve[0]=0;按照拓扑序列往前求各个顶点的ve。如果得到的拓扑序列个数小于网的顶点数n,说明我们建立的图有环,无关键路径,直接结束程序
    3.从终点vn出发,令vl[n-1]=ve[n-1],按逆拓扑序列,往后求其他顶点vl值
    4.根据各个顶点的ve和vl求每个弧的e(i)和l(i),如果满足e(i)=l(i),说明是关键活动。

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

    图的存储结构有两种,邻接矩阵和邻接表。邻接矩阵可以用二维数组来做,邻接表结构是好理解,但结构体的定义还是有一些复杂。
    图是一种比线性表和树更为复杂的数据结构,在这种结构中,任意两个元素之间可能存在关系。
    图的遍历分为深度遍历和广度遍历。算法上有Dljkstra算法,Floyd算法。
    不知怎么的感觉最近学习总是有点赶,可能是最近学得比较理论化的东西且算法多而杂,有点迷糊。

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

    2.1 题目及解题代码

    题目:


    解题代码:

    class Solution {
    public:
        int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
            const int INF = 0x3f3f3f3f;
            vector<int> dist(n, INF);
            vector<int> backup(n);
    
            dist[src] = 0;
            for (int i = 0; i<= K; i++){ 
                backup.assign(dist.begin(), dist.end());
                for (auto &f: flights){ 
                    dist[f[1]] = min(dist[f[1]], backup[f[0]] + f[2]); 
                }
            }
            if (dist[dst] > INF /2) return -1;
            return dist[dst];
        }
    };
    
    

    2.1.1 该题的设计思路

    本题用的是Bellman-Ford算法(似乎是一种高大上的算法,作者只说用了这个算法,没说这个算法怎么用,头大)
    Bellman-Ford算法最关键的部分是松弛操作。即:

          for i = 0 to i<= K
                backup.assign(dist.begin(), dist.end());
                for 枚举所有边
                    更新最短路
                end for;
            end for;
    

    查了一下Bellman-Ford算法的应用:
    处理有负权边的图;
    循环次数的含义:循环K次后,表示不超过K条边的最短距离;
    有边数限制的最短路;
    如果有负权回路,最短路不一定存在;
    Bellman-Ford算法可以求出是否有负环;
    第n循环后,还有更新,说明路径上有n+1个点,也就是存在环,还有更新,说明环是负环;
    循环n次后, 所有的边u->v,权w满足三角不等式:dist[v]<=dist[u]+w;

    时间复杂度:O(n2);

    空间复杂度:O(n2);

    2.1.2 该题的伪代码

    伪代码:

    class Solution {
    public:
        int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
            const int INF = 0x3f3f3f3f;
            定义vector<int> dist(n, INF)表示到起点的最短距离
            定义vector<int> backup(n)为了防止串联
    
            dist[src] = 0;
            for i = 0 to i<= K
                backup.assign(dist.begin(), dist.end());
                for 枚举所有边
                    更新最短路
                end for;
            end for;
            if (dist[dst] > INF /2) return -1;
            return dist[dst];
        }
    };
    
    

    2.1.3 运行结果

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

    优势点,同时也是难点:对Bellman-Ford算法的理解和使用,使代码量大大减少;
    当然有个较大的缺点:复杂度高,运行效率略低。

    2.2 题目及解题代码

    题目:


    解题代码:

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) 
    {
    	vector<int> inDegree(numCourses, 0);
    	vector<vector<int>> lst(numCourses, vector<int>());
    	for (auto v : prerequisites)
    	{
    		inDegree[v[0]]++;	
    		lst[v[1]].push_back(v[0]);	
    	}
    
    	queue<int> que;
    	for (auto i = 0; i < inDegree.size(); i++)
    	{
    		if (inDegree[i] == 0) que.push(i);	
    	}
    
    	vector<int> ans;
    	while (!que.empty())
    	{
    		auto q = que.front();
    		que.pop();
    		ans.push_back(q);
    
    		for (auto l : lst[q])
    		{
    			if (--inDegree[l] == 0) que.push(l);
    		}
    	}
    
    	return ans.size() == numCourses;
    }
    

    2.2.1 该题的设计思路

    本题应用拓扑排序,具体操作如下:
    入度:设有向图中有一结点 v ,其入度即为当前所有从其他结点出发,终点为 v 的的边的数目。
    出度:设有向图中有一结点 v ,其出度即为当前所有起点为 v ,指向其他结点的边的数目。
    每次从入度为 0 的结点开始,加入队列。入度为 0 ,表示没有前置结点。
    处理入度为 0 的结点,把这个结点指向的结点的入度 -1 。
    再把新的入度为 0 的结点加入队列。
    如果队列都处理完毕,但是和总结点数不符,说明有些结点形成环。

    空间复杂度:O(N+e);

    时间复杂度:O(N+e)。

    2.2.2 该题的伪代码

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) 
    {
            遍历边缘列表,计算入度;
    	有向无环DAG图的存储,使用邻接表;
    	for (auto v : prerequisites)
    		初始化入度列表
    		初始化邻接表
    	end for;
    
    	queue<int> que;
    	for  auto i = 0  to i < inDegree.size()
    		将入度为 0 的结点放入队列
    	end for;
    
    	vector<int> ans;
    	while (!que.empty())
    		将入度为0的进行出队;
    		ans.push_back(q);
    
    		for (auto l : lst[q])
    			if --inDegree[l] == 0 
                                  将入度=0的node进队,直到队列为空,且入度都为0了
                            end if;
    		end for;
    	end while;
    
    	return ans.size() == numCourses;
    }
    

    2.2.3 运行结果

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

    优势点同样也是难点:对拓扑排序的引用。拓扑排序的算法简洁程度比BFS和DFS要好,但是在时间和空间复杂度上比较糟糕。

    2.3 题目及解题代码

    题目:



    解题代码:

    class Solution {
    private:
        static constexpr int mod = 1000000007;
    
    public:
        int numOfWays(int n) {
            int fi0 = 6, fi1 = 6;
            for (int i = 2; i <= n; ++i) {
                int new_fi0 = (2LL * fi0 + 2LL * fi1) % mod;
                int new_fi1 = (2LL * fi0 + 3LL * fi1) % mod;
                fi0 = new_fi0;
                fi1 = new_fi1;
            }
            return (fi0 + fi1) % mod;
        }
    };
    

    2.3.1 该题的设计思路

    本题,作者是应用数学公式来求符合该题条件的递推式;
    经计算得到的递推式:

    时间复杂度:O(N);

    空间复杂度:O(1)。

    2.3.2 该题的伪代码

    class Solution {
    private:
        static constexpr int mod = 1000000007;
    
    public:
        int numOfWays(int n) {
            int fi0 = 6, fi1 = 6;
            for i = 2 to i <= n
                带入递推式求解;
                fi0 = new_fi0;
                fi1 = new_fi1;
            end for;
            return (fi0 + fi1) % mod;
        }
    };
    

    2.3.3 运行结果

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

    优势点,同样也是难点:应用数学知识求解递推式,大大降低了时间和空间复杂度,在代码精简度方面自然也是不言而喻。
    与用普通的递推求解相比,该题解(强到爆炸!!!)

    下面是普通递推求解:

    class Solution {
    private:
        static constexpr int mod = 1000000007;
    
    public:
        int numOfWays(int n) {
            vector<int> types;
            for (int i = 0; i < 3; ++i) {
                for (int j = 0; j < 3; ++j) {
                    for (int k = 0; k < 3; ++k) {
                        if (i != j && j != k) {
                            types.push_back(i * 9 + j * 3 + k);
                        }
                    }
                }
            }
            int type_cnt = types.size();
            vector<vector<int>> related(type_cnt, vector<int>(type_cnt));
            for (int i = 0; i < type_cnt; ++i) {
                int x1 = types[i] / 9, x2 = types[i] / 3 % 3, x3 = types[i] % 3;
                for (int j = 0; j < type_cnt; ++j) {
                    int y1 = types[j] / 9, y2 = types[j] / 3 % 3, y3 = types[j] % 3;
                    if (x1 != y1 && x2 != y2 && x3 != y3) {
                        related[i][j] = 1;
                    }
                }
            }
            vector<vector<int>> f(n + 1, vector<int>(type_cnt));
            for (int i = 0; i < type_cnt; ++i) {
                f[1][i] = 1;
            }
            for (int i = 2; i <= n; ++i) {
                for (int j = 0; j < type_cnt; ++j) {
                    for (int k = 0; k < type_cnt; ++k) {
                        if (related[k][j]) {
                            f[i][j] += f[i - 1][k];
                            f[i][j] %= mod;
                        }
                    }
                }
            }
            int ans = 0;
            for (int i = 0; i < type_cnt; ++i) {
                ans += f[n][i];
                ans %= mod;
            }
            return ans;
        }
    };
    

    时间复杂度:O(NT2);
    空间复杂度:O(T2+TN)。

  • 相关阅读:
    C,LINUX,数据结构部分
    LINUX应用开发工程师职位(含答案)
    INT32 System_UserKeyFilter(NVTEVT evt, UINT32 paramNum, UINT32 *paramArray)
    屏幕调试
    1.ARM嵌入式体系结构与接口技术(Cortex-A8版)
    NT9666X调试log
    DemoKit编译过程错误
    selenium 代理设置
    pandas 轮询dataframe
    Spring 定时任务
  • 原文地址:https://www.cnblogs.com/linwei18159070920/p/12832489.html
Copyright © 2011-2022 走看看