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

    0.PTA得分截图

    1.学习总结

    图存储结构

    邻接矩阵

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

    邻接表

    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)

    1、从顶点出发
    2、访问顶点,也就是根节点
    3、依次从顶点的未被访问的邻接点出发,进行深度优先遍历;直至和顶点有路径相通的顶点都被访问
    4、若此时尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止

    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^2)

    广度优先遍历(BFS)

    1、访问初始点v,接着访问v的所有未被访问过的邻接点
    2、按照次序访问每一个顶点的所有未被访问过的邻接点
    3、依此类推,直到图中所有顶点都被访问过
    遍历非连通图

    void  BFS1(AdjGraph *G)
    {      int i;
            for (i=0;i<G->n;i++)     //遍历所有未访问过的顶点
                 if (visited[i]==0) 
                      BFS(G,i);
    }
    

    时间复杂度:O(n+e)

    图应用

    判断图是否连通

    DFS遍历

    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;
    }
    
    BFS() 
    {	int count = 0 ;
    	boolean flag = false;	
    	Queue Q = new Queue();				    
    	visited = new int[this.vertexNum];		
    	while(!Q.isEmpty()) 
    	{       			
    		int s = Q.front();					
    		Q.remove();
    		visited[s] = 1;
    		count ++;							
    		for(int j=0; j<this.vertexNum; j++)
     		{
    			if(this.a[s][j] == 1 && visited[j] == 0 )
    			{
    				    visited[j] = 1;
    			   	    Q.add(j);
    			}											
    		}	
    	}		
    	if(count == this.vertexNum)
    	flag = true;						
     	return flag;
    }
    

    最短路径

    在带权有向图中A点(源点)到达B点(终点)的多条路径中,寻找一条各边权值之和最小的路径,即最短路径

    一个顶点到其余各顶点的最短路径——Dijkstra算法

    引进两个集合S和U。
    S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度)
    U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)

    过程

    1、S只包含起点s;U包含除s外的其他顶点,且U中顶点的距离为”起点s到该顶点的距离”[U中顶点v的距离为(s,v)的长度,如果s和v不相邻,则v的距离为∞]
    2、从U中选出”距离最短的顶点k”,并将顶点k加入到S中;同时从U中移除顶点k
    3、更新U中各个顶点到起点s的距离。之所以更新U中顶点的距离,是因为上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离;
    例如,(s,v)的距离可能大于(s,k)+(k,v)的距离
    4、重复2和3,直到遍历完所有顶点。

    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;	 
          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);	//输出最短路径
    }
    

    时间复杂度O(n^2)

    不适用带负权值的带权图求单源最短路径

    不适用求最长路径长度

    每一对顶点之间的最短路径——Floyd算法

    创建一个二维数组Path路径数组,用于存放任意一对顶点之间的最短路径。每个单元格的内容表示从i点到j点途经的顶点

    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
           }
          }
    }	
    

    时间复杂度为O(n^3)

    空间复杂度为O(n^2)

    最小生成树

    生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。
    一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
    最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

    Prim算法

    “加点法”,每次选择权值最小的边对应的点,加入到最小生成树中。

    #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;
    	        }
        }
    }
    

    Kruskal算法

    “加边法”,初始最小生成树边数为0,每次选择一条满足条件的最小权值边,加入到最小生成树的边集合里。

    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++;			   //扫描下一条边
           }
    }
    

    拓扑排序

    1、从DGA图中找到一个没有前驱的顶点输出
    2、删除以这个点为起点的边。(它的指向的边删除,为了找到下个没有前驱的顶点)
    3、重复上述,直到最后一个顶点被输出。如果还有顶点未被输出,则说明有环

    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;
    	        }
            }
            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
    	  printf("%d ",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;		//找下一个邻接点
    	  }
             }
    }
    

    关键路径

    AOE网:在一个表示工程的带权有向图中,用顶点表示事件(如V0),用有向边表示活动(如<v0,v1> = a1),边上的权值表示活动的持续时间,
    称这样的有向图为边表示的活动的网,简称AOE网(activity on edge network)
    源点:在AOE网中,没有入边的顶点称为源点;如顶点V0
    终点:在AOE网中,没有出边的顶点称为终点;如顶点V3
    AOE网的性质:
    1、只有在进入某顶点的活动都已经结束,该顶点所代表的事件才发生
    2、只有在某顶点所代表的事件发生后,从该顶点出发的各活动才开始
    在AOE网中,所有活动都完成才能到达终点,因此完成整个工程所必须花费的时间(即最短工期)应该为源点到终点的最大路径长度
    具有最大路径长度的路径称为关键路径
    关键路径上的活动称为关键活动

    对图的认识及学习体会

    图对我而言非常的难,我对于其中的知识大部分都不太理解,导致了学习进度慢。
    图这种结构已经非常接近现实了,多对多的关系令它能够做更多的事。

    2.阅读代码

    2.1 最大正方形

        int maximalSquare(vector<vector<char>>& matrix) 
        {
            if (matrix.size() == 0 || matrix[0].size() == 0) 
            {
                return 0;
            }
            int maxSide = 0;
            int rows = matrix.size(), columns = matrix[0].size();
            for (int i = 0; i < rows; i++) 
            {
                for (int j = 0; j < columns; j++)        
                {
                    if (matrix[i][j] == '1') 
                    {
                        // 遇到一个 1 作为正方形的左上角
                        maxSide = max(maxSide, 1);
                        // 计算可能的最大正方形边长
                        int currentMaxSide = min(rows - i, columns - j);
                        for (int k = 1; k < currentMaxSide; k++) 
                        {
                            // 判断新增的一行一列是否均为 1
                            bool flag = true;
                            if (matrix[i + k][j + k] == '0')  break;
                            for (int m = 0; m < k; m++) 
                            {
                                if (matrix[i + k][j + m] == '0' || matrix[i + m][j + k] == '0') 
                                {
                                    flag = false;
                                    break;
                                }
                            }
                            if (flag) maxSide = max(maxSide, k + 1);
                            else      break;
                        }
                    }
                }
            }
            int maxSquare = maxSide * maxSide;
            return maxSquare;
        }
    

    2.1.1 设计思路

    由于正方形的面积等于边长的平方,因此要找到最大正方形的面积,首先需要找到最大正方形的边长,然后计算最大边长的平方即可。
    1、遍历矩阵中的每个元素,每次遇到1,则将该元素作为正方形的左上角;
    2、确定正方形的左上角后,根据左上角所在的行和列计算可能的最大正方形的边长(正方形的范围不能超出矩阵的行数和列数),在该边长范围内寻找只包含 1 的最大正方形;
    3、每次在下方新增一行以及在右方新增一列,判断新增的行和列是否满足所有元素都是1。

    时间复杂度:O(mn*min(m,n)^2),其中 m 和 n 是矩阵的行数和列数。
    空间复杂度:O(1)

    2.1.2 伪代码

    int maximalSquare(vector<vector<char>>& matrix) 
        {
            if (空矩阵)
                 return 0;
            定义整型变量maxside记录最长边的大小
            记录矩阵行列大小
            for (int i = 0; i < rows; i++) 
            {
                for (int j = 0; j < columns; j++)        
                {
                    if (遇到一个 1 作为正方形的左上角) 
                    {
                        计算可能的最大正方形边长
                        int currentMaxSide = min(rows - i, columns - j);
                        for (int k = 1; k < currentMaxSide; k++) 
                        {
                            判断新增的一行一列是否均为 1
                        }
                    }
                }
            }
            int maxSquare = maxSide * maxSide;
            return maxSquare;
        }
    

    2.1.3 运行结果

    2.1.4 解题优势及难点

    此题使用暴力法,难点在于如何降低时间复杂度。
    解题优势在于对面积较小的正方形的遍历不会重复遍历邻接矩阵

    2.2 找到最终的安全状态

    class Solution {
    public:
        vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
            int n = graph.size();
            vector<int> outDegree(n, 0); // 维护出度
            vector<vector<int>> revGraph(n, vector<int>{});
            vector<int> ans;
            for (int i =0; i < n; i++){
                outDegree[i] = graph[i].size();
                for (auto &end : graph[i]){
                    revGraph[end].push_back(i);
                }
            }
            queue<int> q;
            for (int i =0; i< n ; i++){
                if (outDegree[i] == 0) q.push(i);
            }
            while (!q.empty()){
                int f = q.front();
                ans.push_back(f);
                q.pop();
                for (auto start: revGraph[f]){
                    outDegree[start]--;
                    if (outDegree[start] == 0) q.push(start);
                }
            }
            sort(ans.begin(), ans.end());
            return ans;
        }
    };
    

    2.2.1 设计思路

    定义安全的点:路径终点,也就是出度为0的点
    定义最终安全的点:从起始节点开始,可以沿某个路径到达终点,那么起始节点就是最终安全的点。

    1、找到出度为0的顶点,这些点是安全的点
    2、逆向删除以出度为0的顶点为弧头的边,弧尾的出度减一
    3、重复上面两步,直到不存在出度为0的顶点

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

    2.2.2 伪代码

        遍历顶点
    	if(顶点v出度度为0)
    	    v入队列
        while(队列非空)
        {
    	top出队
    	遍历逆向邻接表
    	{
                top所有的邻接点出度减一
    	    if (邻接点出度为0)
    		    邻接点入队
    	}
    }
    

    2.2.3 运行结果

    2.2.4 解题优势及难点

    优势:使用拓扑排序,而不是BFS或DFS
    难点:逆向边的操作

    2.3 不邻接植花

    class Solution {
    public:
        //static const int MAXV=10000;
        //int G[MAXV][MAXV]={0};
        vector<int> gardenNoAdj(int N, vector<vector<int>>& paths) {
            vector<int> G[N];
            for (int i=0; i<paths.size(); i++){//建立邻接表
                G[paths[i][0]-1].push_back(paths[i][1]-1);
                G[paths[i][1]-1].push_back(paths[i][0]-1);
            }
            vector<int> answer(N,0);//初始化全部未染色
            for(int i=0; i<N; i++){
                set<int> color{1,2,3,4};
                for (int j=0; j<G[i].size(); j++){
                    color.erase(answer[G[i][j]]);//把已染过色的去除
                }
                answer[i]=*(color.begin());//染色
            }
            return answer;
        }
    };
    

    2.3.1 设计思路

    1、根据paths建立邻接表;
    2、默认所有的花园先不染色,即染0;
    3、从第一个花园开始走,把与它邻接的花园的颜色从color{1,2,3,4}这个颜色集中删除;
    4、删完了所有与它相邻的颜色,就可以把集合中剩下的颜色随机选一个给它了,为了简单,将集合中的第一个颜色赋给当前花园;
    5、循环3和4到最后一个花园。

    2.3.2 伪代码

            建立邻接表
            初始化全部花园未种花answer(N,0);
            for i=0 to N-1 do
                每次循环都要初始化color
                for j=0 to G[i].size()-1 do
                    把已染过色的去除
                end for
                对当前顶点种花answer[i]=*(color.begin());
            end for
            return answer;
        }
    };
    

    2.3.3 运行结果

    2.3.4 解题优势及难点

    优势:使用邻接表存储数据
    难点:color的应用及操作

  • 相关阅读:
    Lucky Substrings
    KMP
    圆桌问题(hdu4841)
    codeforces 624C Graph and String
    Joseph(hdu1443)
    The Longest Straight(FZUoj2216)
    C1. 组队活动 Small(BNUOJ)
    A1. 道路修建 Small(BNUOJ)
    Problem 2221 RunningMan(fuzoj)
    CODEFORCEs 621E. Wet Shark and Blocks
  • 原文地址:https://www.cnblogs.com/chenlr/p/12833206.html
Copyright © 2011-2022 走看看