zoukankan      html  css  js  c++  java
  • 0. PTA得分截图

    1.本周学习总结

    1.1 总结图内容

    图的概念:

    线性结构可以看成是树形结构的特殊情况。

    树形结构可以看成是图形结构的特殊情况。

    图形结构是最普遍的 一类数据结构,具有广泛的实际应用。

    图的分类:

    有向图:弧是有方向的边

    无向图:没方向的边

    完全图:

    无向图:有n(n-1)/2条边

    有向图:有n(n-1)条边

    稠密图:当一个图接近完全图时,则称为稠密图

    稀疏图:当一个图的边e<<n(n-1)时,则称为稀疏图

    强连通图:有向图中的任意两个顶点,都有去往彼此的路径。

    强连通分量:各个强连通子图称作它的强连通分量。如下图,有三个强连通分量·

    img

    图的基本术语:

    度:

    无向图:以该顶点为端点的边数成为该顶点的度

    有向图:

    入度:以顶点i为终边的入边的数目,称为该顶点的入度

    出度:以顶点i为起始点的出边的数目,称为该顶点的出度

    图的存储结构:

    邻接矩阵:

    • 邻接矩阵用二维数组进行表示
    • 每个存储单元存放着顶点i与顶点j的关系,1表示直连,0表示无连边
    • 对于无向图的邻接矩阵,会发现关于对角线对称。
    • 时间复杂度:O(n^2)

    邻接矩阵存储类型的定义及创建邻接矩阵

    #defineMAXV
    //声明顶点的类型
    typedef struct  
    {
          int no;       //顶点编号
          InfoType info;    //顶点其它信息
    }VetexType;
    声明邻接矩阵的类型
    typedef struct   //图的定义
    {
        int edges[MAXV][MAXV];  //邻接定义
        int n,e;        //顶点数,边数
        VertexType vexs[MAXV];  //存放顶点信息
    }MatGraph;
    MatGraph g;  //声明邻接矩阵存储的图
    void CreateMGraph(MGraph& g, int n, int e)//建图 
    {
    	int i, j;
    	int a, b;
    	
    	for ( i = 0; i < n; i++)
    	{
    		for (j = 0; j < n; j++)
    		{
    			g.edges[i][j] = 0;
    		}
    	}
    	for (i = 0; i < e; i++)
    	{
    		cin >> a >> b;
    		g.edges[a - 1][b - 1] = b;
    		g.edges[b - 1][a - 1] = a;
    	}
    	g.e = e;
    	g.n = n;
    
    }
    

    邻接表:

    • 对于每个顶点i建立一个单链表,将顶点i的所有邻接点用链存储起来。
    • 时间复杂度:O(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;
        
    }AdjGraph;
    AdjGraph *G;//声明一个邻接表存储的图G
    void CreateAdj(AdjGraph*& G, int n, int e)//创建图邻接表
    {
    	int i, j, a, b;
    
    	ArcNode* p;
    	G = new AdjGraph;
    
    	for (i = 0; i < n; i++)
    	{
    		G->adjlist[i].firstarc = NULL;//给邻接表中所有头结点的指针域置初值
    	}
    
    	for (i = 0; i < e; i++)//根据输入边建图
    	{
    		cin >> a >> b;
    		p = new ArcNode;
    		p->adjvex = a;
    		p->nextarc = G->adjlist[b].firstarc;
    		G->adjlist[b].firstarc = p;
    
    	}
    	G->e = e;
    	G->n = n;
    
    }
    void DelAdj(AdjGraph*& G, int n, int e) //删除邻接表
    {
    	ArcNode* p, * q;
    
    	for (int i = 1; i <= n; i++)
    	{
    		p = G->adjlist[i].firstarc;
    
    		while (p != NULL)
    		{
    			q = p;
    			p = p->nextarc;
    			delete q;
    		}
    	}
    	delete[] G->adjlist;
    }
    

    图的遍历及应用

    连通图的深度搜索遍历:

    (1)从图中 某个初始顶点v出发,首先访问初始顶点v

    (2)选择一个与顶点v相邻且没被访问过的顶点w为初始顶点,再从w出发进行深度优先搜索,直到图中与当前顶点v邻接的所有顶点都被访问过为止。

    void DFS(ALGraph *G,int v)
    {
         ArcNode *p;
         visited[v]=1;
         cout<<" "<<v;
         p=G->adjlist[v].firstarc;
         while(p!=NULL)
         {
            if(visited[p->adjvex]==0)
               DFS(G,p->adjvex);
               p=p->nextarc;
         }
    }
    

    非连通图的深度优先搜索遍历

    void DFSTraverse(Graph G)
    {
      for(v=0;v<G.vexnum;++v)
      {
          visited[v]=FALSE;
      }
      for(v=0;v<G.vexnum;v++)
      {
          if(!visited[v])
            DFS(G,v);
      }
    }
    

    连通图的广度优先搜索遍历

    (1)访问初始点v,接着访问v的所有未被访问过的邻接点。

    (2)按照次序访问每一个顶点的所有未被访问过的邻接点。

    (3)一次类推,直到图中所有顶点都被访问过。

    int visited[MAXV];
    void DFS(ALGraph *G,int v)
    {
       ArcNode *p;
       visited[v]=1;
       cout<<" "<<v;
       p=G->adjlist[v].firstarc;
       while(p!=NULL)
       {
          if(visited[p->adjlist]==0)GFS(G,p->adjvex);
          p=p->nextarc;
       }
    }
    

    非连通图的广度优先搜索遍历

    void BFS(AdjGraph *G)
    {
        int i;
        for(i=0;i<G->n;i++)
        {
             if(visited[i]==0)
             {
                BFS(G,i);
             }
        }
    }
    
    • 调用BFS()的次数,恰好等于连通分量的个数

    采用BFS遍历方式判断无向图是否连通

    int map[MAXV][MAXV];
    bool Connected(int v)
    {
         int count=0;
         int i.j;
         queue<int>q;
         q.push(v);
         mark[v]=1;
         while(!q.empty())
         {
            v=q.front();
            q.pop();
            mark[v]=1;
            count++;
            for(i=1;i<=n;i++)
            {
                if(!mark[i]&&map[v][i]!=0)
                {
                     q.push(i);
                     mark[i]=1;
                }
            }
         }
         if(count==0)
         {
            return true;
         }
         else 
            return false;
    }
    

    采用DFS遍历方式判断无向图是否连通

    bool Connect(AdjGraph *G)
    {
        int i;
        bool flag=true;
        
        for(i=0;i<G->n;i++)
        {
           visited[i]=0;
        }
        DFS(G,0);//从0开始深度遍历
        for(i=0;i<G->n;i++)
        {
           flag=false;
           break;
        }
        return flag;
    }
    

    广度优先遍历找到的路径一定是最短路径,而深度优先遍历则不一定。

    深度优先遍历能找到所有路径,而广度优先遍历难以实现。

    查找最短路径

    查找简单路径

    void FindAllPath(AGraph *G,int u,int v,int path[],int d)
    {
       //d表示path中的路径长度,初始值为-1
       int w,i;
       ArcNode *p;
       d++;
       path[d]=u;  //路径长度d到u的距离
       visited[u]=1;
       if(u==v&&d>=1)//找到一条路
       {
            for(i=0;i<=d;i++)
            {
               cout<<" "<<path[i];
         
            }
              cout<<endl;
       }
       p=G->adjlist[u].firstarc;
       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;//恢复环境
    }
    

    Dijkstras算法(单源最短路径)

    1.用邻接矩阵G来表示带权有向图,dis[j]表示从选定的某点v0与j的最短距离,

    2.对dis[]进行初始化,dis[i]=G[v] [i]

    3.从dis[]里面找最小的dis[j]值(不包括之前找过的点),表示当前求得的一条从v0出发到vj最短路径

    然后查看与顶点j相连的其它没被访问的点k,是否与v0直连,如果直连,则比较dis[k]与dis[j]+G[j] [k]

    的大小,如果dis[j]+G[v] [k]更小,则说明vo经过中间点j到k比vo直接到k更短,则把dis[k]的值改为dis[j]+G[j] [k]

    4.重复上面的操作,知道所有顶点都访问过

    5.对于path数组,对path[]数组进行初始化,与顶点v0直连的,path[j]=v0,,不是直连的,置为-1

    6.如果发现dis[k]与dis[j]+G[j] [k],则说明vo经过中间点j到k比vo直接到k更短,所以把path[k]的值复制为j,因为path[j]=k的含义表示的是j的上一个全局上距离最短的顶点式k

    做这个,要注意下标代表的含义

    • 0 1 2 3 4 5 0 1 2 3 4 5
      S U dis[] path[]
      0 1,2,3,4,5 0 1 5 2 ∞ ∞ 0 0 0 0 -1 -1
      0,1 2,3,4,5 0 1 4 2 8 ∞ 0 0 1 0 1 -1
      0,1,3 2,4,5 0 1 4 2 8 10 0 0 1 0 1 3
      0,1,3,2 4,5 0 1 4 2 8 10 0 0 1 0 1 3
      0,1,3,2,4 5 0 1 4 2 8 10 0 0 1 0 1 3
      0,1,3,2,4,5 0 1 4 2 8 10 0 0 1 0 1 3
    void Dijkstra(MatGraph g,int v)
    {
        int dist[MAXV],path[MAXV];
        int visited[MAXV];
        int mindis,j,ul
        for(i=0;i<g.n;i++)
        {
             dist[i]=g.edges[v][i];  //距离初始化
             visited[i]=0;
             if(g.edges[v][i]<INF)
             {
                 path[i]=v;    //顶点v到i有边
             }
             else
             {
                 path[i]=-1;    //顶点v到i边
             }
        }
        visited[v]=1;
        
        for(i=0;i<g.n;i++)
        {
            mindis=INF;
               for(j=0;j<g.n;j++)
               {
                  if(s[j]==0&&dist[j]<mindis)   //找最小路径长度顶点u
                  {
                     u=j;
                     mindis=dist[j];
                  }
               }
               visited[u]=1;     //顶点u加入s中
               for(j=0;j<g.n;j++)
               {
                   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);
    }
    
    Dijkstras算法(单源最短路径)特点
    • 不适用带负权值的带权图求单源最短路径
    • 不适用于求最长路径长度
    • 时间复杂度:O(n^2)

    Floyd算法->所有顶点间的最短路径

    • 我觉得这个算法,其实和Dijkstras算法(单源最短路径)本质上是一样的东西
    • 这里介绍一下,两个数组的含义,A[i] [j]存放的是从i到j之间的的最短距离,A[i] [j]>A[i] [k]+A[k] [j],表示的是从i到j之间经过k会更短,所以把A[i] [j]的长度改为A[i] [k]+A[k] [j],path[i] [j]=i,表示的是,全局最短路径中,j顶点的上一个顶点是i。
    • 时间复杂度:O(n^3)
    void Floyd(MatGraph g)
    {
       int A[MAXVEX][MAXVEX];//建立A数组
       int path[MAXVEX][MAXVEX];
       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
                  path[i][j]=-1;    //i和j顶点之间没有一条边
            }
       }
       for(k=0;k<g.n;k++)
       {
          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
                }
             }
          }
       }
    }
    

    Bellman-Ford(贝尔曼-福特)

    Dijkstra算法是处理单源最短路径的有效算法,但是只局限于边的权值非负的情况,若图中出现权值为负的,则不能实现最短路径,这个算法可以实现

    1.数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为, Distant[s]为0;

    2.以下操作循环执行至多n-1次,n为顶点数:
    对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
    若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;

    3.为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。

    松弛计算:

    松弛计算之前,点B的值是8,但是点A的值加上边上的权重2,得到5,比点B的值(8)小,所以,点B的值减小为5。这个过程的意义是,找到了一条通向B点更短的路线,且该路线是先经过点A,然后通过权重为2的边,到达点B。
    当然,如果出现一下情况:

    则不会修改点B的值,因为3+4>6。

    Bellman-Ford算法可以大致分为三个部分
    第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
    第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
    第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:d(v) > d (u) + w(u,v),存在则返回false,表示途中存在从源点可达的权为负的回路。
    之所以需要第三部分,是因为,如果存在从源点可达的权为负的回路。则应为无法收敛而导致不能求出最短路径。
    考虑如下的图:

    经过第一次遍历后,点B的值变为5,点C的值变为8,这时,注意权重为-10的边,这条边的存在,导致点A的值变为-2。(8+ -10=-2)

    第二次遍历后,点B的值变为3,点C变为6,点A变为-4。正是因为有一条负边在回路中,导致每次遍历后,各个点的值不断变小。

    在回过来看一下bellman-ford算法的第三部分,遍历所有边,检查是否存在d(v) > d (u) + w(u,v)。因为第二部分循环的次数是定长的,所以如果存在无法收敛的情况,则肯定能够在第三部分中检查出来。比如

    此时,点A的值为-2,点B的值为5,边AB的权重为5,5 > -2 + 5. 检查出来这条边没有收敛。

    所以,Bellman-Ford算法可以解决图中有权为负数的边的单源最短路径问。

    int N, M;
    typedef struct node
    {
        int u, v;
        int cost;
    } E;
    node E[N];
    int dis[N], pre[N];
    bool Bellman()
    {
        int ok;
        for(int i = 1; i <= N; ++i)
            dis[i] = (i == 1 ? 0 : MAX);
        for(int i = 1; i <= N - 1; ++i)
        {
            ok=1;
            for(int j = 1; j <= M; ++j)
                if(dis[E[j].v] > dis[E[j].u] + E[j].cost)
                {
                    dis[E[j].v] = dis[E[j].u] + E[j].cost;
                    ok=0;
                }
            if(ok==1)
                break;
        }
        bool flag = 1;
        for(int i = 1; i <= M; ++i)
            if(dis[E[i].v] > dis[E[i].u] + E[i].cost)
            {
                flag = 0;
                break;
            }
        return flag;
    }
    int main()
    {
        cin>>N>>M;
        for(int i = 1; i <= M; ++i)
            cin>>E[i].u>>E[i].v>>E[i].cost;
        if(Bellman())
            cout<<dis[M];
        else
            cout<<"存在负";
        return 0;
    }
    
    

    最小生成树

    最短路径与最小生成数不同,路径上不一定包含n个顶点

    一个连通图的生成树是一个极小连通子图,它含有图中n个顶点和构成一棵树的n-1条边,不能回路

    一个连通图的生成树不一定是唯一的

    权值和最小的生成树称作最小生成树

    最小生成树不一定唯一,但最小生成树的权值之和一定相同

    Prim算法

    #define INF 0x3f3f3f
    void Prim(MGraph g,int v)
    {
        int lowcost[MAXV],min,closest[MAXV],i,j,k;
        
        for(i=0;i<g.n;i++)
        {
            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记录最近顶点编号
                  k=j;
               }
            }
            lowcost[k]=0;   //标记k已经加入U
            for(j=0;j<g.n;j++)     //修正
            {
                 if(lowcost[j]!=0&&g.edges[k][j]<lowcost[j])
                 {
                       lowcost[j]=g.edges[k][j];
                       closest[j]=k;
                 }
            }
        }
    }
    

    特点:

    • 局部最优(贪心算法)+调整=全局最优
    • 贪心算法:只顾或者眼前最大的利益,有时不一定是最优解
    • 时间复杂度:O(n^2),适用于稠密图
    • 应用:公路村村通

    Kruskal算法



    #include<iosream>
    #include<vector>
    #include<algorithm>
    #define MAX N 100
    using namespace std;
    struct Node 
    {
       int numr;
       int numd;
       int val;
    }
    int cmp(Node a,Node b)  //对vector存储内容进行由小到大排序
    {
      return a.val<b.val;
    }
    vector<Node>v;//保存所有边的信息
    int F[MAX_N];
    int findPar(int n)
    {
       if(F[n]=n)
       {
          return F[n];
       }
       else
       {
           F[n]=findPar(F[n]);//减少下次查找的时间
           return F[n];
       }
    }
    /*如果两个节点不在用一个连通分支中,则合并,并修改其中一棵树根节点的父亲节点 */
    int merges(int a,int b)
    {
       int x=findPar(a);
       int y=findPar(b);
       if(x==y)
       {
          return 1;
       }
       else
       {
          F[y]=x;
          return 0;
       }
    }
    int main()
    {
        for(int i=0;i<MAX;i++)
        {
           F[i]=i;
        }
        int m;
        cin>>m;
        for(int i=0;i<m;i++)
        {
            Node nod;
            cin>>nod.numr>>nod.numd>>nod.val;
            v.push_back(nod);
        }
        sort(v.begin(),v.end(),cmp);//权值由小到大排序
        for(int i=0;i<v.size();i++)
        {
             if(!merges(v[i],numr,v[i].numd))
             {
                 v1.push_back(v[i]);
             }
        }
    }
    

    特点:

    • 与并查集进行结合
    • 时间复杂度O(elog2e),适用于稀疏图
    • 应用:公路村村通

    拓扑排序

    1.从有向图中选取一个没有前驱的顶点,并输出之

    2.从有向图中删除此顶点以及所有以他为尾的弧

    3.重复上述两步,直至图空,或者图不空但找不到无前驱的顶点为止


    typedef struct
    {
        vertex data;
        int count;
        ArcNode *firstarc;
    }VNode;
    void TopSort(ADjGraph *G)
    {
       int i,j;
       int St[MAXV];
       int top=-1;
       ArcNode *p;
       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--;
          cout<<i;
          p=G->adjlist[j].firstarc;
          while(P!=NULL)
          {
            j=p->adjvex;
            G->adjlist[j].count--;
            if(G->adjlist[j].count==0)
            {
               top++;
               St[top]=j;
            }
            p=p->nextarc;
          }
       }
    }
    

    特点:

    • 在输出顶底序号的地方加入一个初始化为0的cnt,进行cnt++,如果最后cnt<n,则说明有回路
    • 时间复杂度:O(n+e)
    • 一个AOV-网的拓扑序列不是唯一的
    • 应用:可以检测是否有环

    关键路径

    用顶点表示事件,用有向边e表示活动,边的权表示活动持续的时间,是带权的有向无环图

    关键路径:从有向图的源点到汇点的最长路径

    关键活动:关键路径中的边

    源点:入度为0

    汇点:出度为0

    事件的最早开始时间:事件v的最早开始事件,一定是所有前驱事件x,y,z完成,才轮到事件v

    ve(v)=max{ve(x)+a,ve(y)+b,ve(z)+c}(从左往右)

    事件的最迟开始时间:要保证后继所有事件能按时完成,取最小

    vl(v)=min{vl(x)-a,vl(y)-b,vl(z)-c}(从右往左)

    事件 ve(取max) vl(取min)
    1 0 0
    2 19 19
    3 15 15
    4 29 37
    5 38 38
    6 43 43
    • 特点:关键路径是1->2->3->5->6,因为ve=vl,没有富余的时间
    活动<v1,v2> <1,2> <1,3> < 3,2> <2,4> <2,5> < 3,5> <4,6> <5,6>
    e=v1e 0 0 15 19 19 15 29 38
    l=v2l-weight 17 0 15 27 19 27 37 38
    l-e 17 0 0 8 0 12 8 0
    • 特点:关键活动是l-e=0

    1.2谈谈你对图的认识和看法

    图,这个章节,我们会看到我们开始对于二维数组的认识更深一步,并且,这个章节学习的算法,也比前面几个章节多很多,我们求最短路径,在实际问题里应用甚广,拓扑排序对问题的缓急进行了详细的说明,等等,还有很多算法,我很佩服这些研究算法的人,因为得出一个正确的算法,要经过大量的计算,不只是针对某一个特例,而是对大部分的问题都适用,这些,都要有很扎实的数学功底,并且有耐心的接受来自别人的质问,耐心的接受每一次失败,所以,没有哪个伟人是轻轻松松的,转眼间,我们就已经学到图了,也很快,我的大学的第二个学期也快过去了,可是感觉自己这个学期,什么都没有学到,在家里,玩的玩,吃的吃,这些暂时的快乐总是让我的心很不安,因为你在轻松的时候,别人在努力学习,一步一步的进步,而自己却一步一步的退步,其实心里五味杂陈,不知如何是好,太难了,希望疫情快快过去,,让我的第二个学期的大学生活,可以在大学里说结束。

    2.阅读代码

    我的阅读代码作业——动态规划

  • 相关阅读:
    洛谷 P5595 【XR-4】歌唱比赛
    洛谷 P5594 【XR-4】模拟赛
    洛谷 AT1350 深さ優先探索
    洛谷 P1449 后缀表达式
    VIJOS-P1064 迎春舞会之数字舞蹈
    洛谷 P3998 [SHOI2013]发微博
    CF306C White, Black and White Again
    CF261E Maxim and Calculator
    JLOI 2014 松鼠的新家
    HNOI 2009 有趣的数列
  • 原文地址:https://www.cnblogs.com/-sushi/p/12819206.html
Copyright © 2011-2022 走看看