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

    DS博客作业04--图


    这个作业属于哪个班级 数据结构--网络2011/2012
    这个作业的地址 DS博客作业04--图
    这个作业的目标 学习图结构设计及相关算法
    姓名 姚庆荣

    0.PTA得分截图

    1.本周学习总结(6分)

    1.1 图的存储结构

    图的存储主要分两种,一种邻接矩阵,另一种邻接表
    用不同的方式存储图的信息

    1.1.1 邻接矩阵

    无向图: 如果一个图结构中,所有的边都没有方向性,那么这种图便称为无向图。

    • 结构体定义:
    typedef struct              //图的定义
    {  int edges[MAXV][MAXV];     //邻接矩阵
       int n,e;              //顶点数,弧数
    } MGraph;  
    //另一种表示方式
    typedef struct {
    	int** edges;//邻接矩阵
    	int n, e;//顶点数,边数
    }MGraph;
    
    • 建图函数:

      void CreateAdj(AdjGraph*& G, int A[MAXV][MAXV], int n, int e)
      {
      	int i, j;ArcNode* p;
      	G = (AdjGraph*)malloc(sizeof(AdjGraph));
      	for (i = 0;i < n;i++)
      		g->adjlist[i].firstarc = NULL;
      	for (i = 0;i < n;i++)
      		for(j=n-1;j>=0;j--)
      			if (A[i][j] != 0 && A[i][j] != INF)
      			{
      				p = (ArcNode*)malloc(sizeof(ArcNode));
      				p->adjvex = j;
      				p->weight = A[i][j];
      				p->nextarc = G->adjlist[i].firstarc;
      				G->adjlist[i].firstarc = p;
      			}
      	G->n = n; G->e = e;
      }
      

    有向图: 一个图结构中,边是有方向性的,那么这种图就称为有向图。

    • 有向图和无向图的区别:两者的存储结构相同,但由于有向图的边有方向,因此有向图的边关系处理一次即可。

    1.1.2 邻接表

    • 结构体定义

      typedef struct ANode
      {  int adjvex;            //该边的终点编号
         struct ANode *nextarc;    //指向下一条边的指针
         int info;    //该边的相关信息,如权重
      } ArcNode;                //边表节点类型
      typedef int Vertex;
      typedef struct Vnode
      {  Vertex data;            //顶点信息
         ArcNode *firstarc;        //指向第一条边
      } VNode;                //邻接表头节点类型
      typedef VNode AdjList[MAXV];
      typedef struct 
      {  AdjList adjlist;        //邻接表
         int n,e;        //图中顶点数n和边数e
      } AdjGraph;   
      
    • 建图函数

      void CreateAdj(AdjGraph *&G, int n, int e)//创建图邻接表
      {
      	int i, j, a, b;
      	G = new AdjGraph;
      	for (i = 1; i <= n; i++)//邻接表头结点置零
      	{
      		G->adjlist[i].firstarc = NULL;
      	}
      	for (j = 1; j <= e; j++)//无向图
      	{
      		cin >> a >> b;	
      		ArcNode *p,*q;
      		p = new ArcNode;
      		q = new ArcNode;
      		p->adjvex = b;//用头插法进行插入
      		q->adjvex = a;
      		p->nextarc = G->adjlist[a].firstarc;
      		G->adjlist[a].firstarc = p;
      		q->nextarc = G->adjlist[b].firstarc;
      		G->adjlist[b].firstarc = q;
      	}
      	G->n = n;
      	G->e = e;
      }
      

    1.1.3 邻接矩阵和邻接表表示图的区别

    1. 邻接矩阵的空间复杂度为0(n2),而邻接表的空间复杂度为0(n+e)。
    2. 如果图中边的数目远远小于n2称作稀疏图,这时用邻接表表示比用邻接矩阵表示节省空间;
      如果图中边的数目接近于n2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。因此当数据的边关系较为复杂时用邻接矩阵,稀疏时用领接表,内存空间不会浪费。

    1.2 图遍历

    1.2.1 深度优先遍历

    深度遍历图解
    1:需要遍历的图
    1:从A点出发,红色代表A点被访问。

    2:有A->B,A->C都可行,都可以遍历,这里我选择遍历C。

    3:再遍历C之后,也可遍历C->B,C->D 我这里选择遍历C->D。

    4:现在由D->E 这里遍历了E。

    5:E已经不可以再遍历了。然后返回D,D又不可以遍历B,放回C,然后C->B可以。所以从C->B 就实现遍历。

    • 深度遍历代码

      1. 矩阵

        void DFS(MGraph g, int v)//深度遍历 
        {
            int flag = 0;
            visited[v] = 1;
            for (int j = 0; j <g.n; j++)
            {
                if (visited[j] == 1)
                {
                    flag++;
                }
            }
            if (flag == 1)
                cout << v;
            else
                cout << " " << v;
            for (int i = 0; i < g.n; i++)
            {
                if (g.edges[v][i] != 0 && visited[i] == 0)
                {
                    DFS(g, i);
                }
            }
        }
        
      2. 链表

        void DFS(AdjGraph* G, int v)//v节点开始深度遍历 
        {
            int flag = 0;
            visited[v] = 1;
            for (int j = 1; j <= G->n; j++)
            {
                if (visited[j] == 1)
                {
                    flag++;
                }
            }
            if (flag == 1)
                cout << v;
            else
                cout << " " << v;
            
            ArcNode* p;
            
            for (int i = 0; i < G->n; i++)
            {
                p = G->adjlist[v].firstarc;
                while (p)
                {
                    if (visited[p->adjvex] == 0&&p!=NULL)
                    {
                        DFS(G, p->adjvex);
                    }
                    p = p->nextarc;
                }
            }
        }
        
    • 深度遍历适用哪些问题的求解。(可百度搜索)

      深度遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。

      问题类型:1.全排列问题;2. ABC+DEF=GHI 问题 ;3.二维数组寻找最短路径问题;4.求岛屿的面积.

    1.2.2 广度优先遍历

    • 广度遍历代码

      1. 矩阵

        void BFS(MGraph g, int v)//广度遍历 
        {
            int front;
            queue<int>q;
            q.push(v);
            visited[v] = 1;
            cout << v;
            while (!q.empty())
            {
                front = q.front();
                q.pop();
                for (int i = 0; i < g.n; i++)
                {
                    if (g.edges[front][i] == 1 && visited[i] == 0)
                    {
                        q.push(i);
                        visited[i] = 1;
                        cout << " " << i +;
                    }
                }
            }
        
        }
        
      2. 链表

        void BFS(AdjGraph* G, int v) //v节点开始广度遍历  
        {
            int i, j;
            int front;
            queue<int>q;
            ArcNode* p;
            q.push(v);
            visited[v] = 1;
            cout << v;
            while (!q.empty())
            {
                front = q.front();
                q.pop();
                p = G->adjlist[front].firstarc;
                do
                {
                    if (p != NULL&&visited[p->adjvex]==0)
                    {
                        q.push(p->adjvex);
                        visited[p->adjvex ] = 1;
                        cout << " " << p->adjvex ;
                    }
                    p = p->nextarc;
                }while (p != NULL);
            }
        }
        
    • 广度遍历适用哪些问题的求解。(可百度搜索)

      广度遍历:BFS算法对于解决最短路径问题比较有效。

      问题类型:1.求取最短路径问题;2.求迷宫路径;哪些问题的求解。(可百度搜索)

    1.3 最小生成树

    有n个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个连通网,当构造这个连通网所花的权值最小时,搭建该连通网的生成树,就称为最小生成树。

    1.3.1 Prim算法求最小生成树

    • Prim算法的步骤(引用)

      第一步:随意选取起点

    ​ 图中有9个顶点v1-v9,集合表示为:V={v1,....,V9},每条边的边权值都在图上;在进行prim算法时,我们先选择v1作为起始点,现在我们设U集合为当前所找到最小生成树里面的顶点,TE集合为所找到的边。

    ​ 状态如下:U={v1}; TE={};

    普利姆算法(prim)求最小生成树(MST)过程详解

    ​ 第二步:在前一步的基础上寻找最小权值

    ​ 查找一个顶点在U={v1}集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。

    ​ 通过图中我们可以看到边v1-v8的权值最小为2,那么将v8加入到U集合,(v1,v8)加入到TE。

    ​ 状态如下:U={v1,v8}; TE={(v1,v8)};

    普利姆算法(prim)求最小生成树(MST)过程详解

    ​ 第三步:继续寻找最小权值

    ​ 查找一个顶点在U={v1,v8}集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。 通过图中我们可以看到边v8-v9的权值最小为4,那么将v9加入到U集合,(v8,v9)加入到TE。

    ​ 状态如下:U={v1,v8,v9}; TE={(v1,v8),(v8,v9)};

    普利姆算法(prim)求最小生成树(MST)过程详解

    ​ 第四步:在前一步的基础上,继续寻找最小权值

    ​ 查找一个顶点在U={v1,v8,v9}集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。 通过图中我们可以看到边v9-v2的权值最小为1,那么将v2加入到U集合,(v9,v2)加入到TE。

    ​ 状态如下:U={v1,v8,v9,v2};

    ​ TE={(v1,v8),(v8,v9),(v9,v2)};

    普利姆算法(prim)求最小生成树(MST)过程详解

    ​ 第五步:继续在前一步的基础上,寻找最小权值

    ​ 查找一个顶点在U={v1,v8,v9,v2}集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。通过图中我们可以看到边v2-v3的权值最小为3,那么将v3加入到U集合,(v2,v3)加入到TE。

    ​ 状态如下:U={v1,v8,v9,v2,v3};

    ​ TE={(v1,v8),(v8,v9),(v9,v2),(v2,v3)};

    普利姆算法(prim)求最小生成树(MST)过程详解

    ​ 第五~九步:继续在前一步的基础上,寻找最小权值

    ​ 如此循环一下直到找到所有顶点为止。到这大家应该对普利姆算法求解最小生成树的过程有所知晓,但需注意以下三点:

    (1)每次都选取权值最小的边,但不能构成回路,构成环路的边则舍弃。如图中的(v1,v9),(v1,v2)等构成回路舍弃

    (2)遇到权值相等,又均不构成回路的边,随意选择哪一条,均不影响生成树结果。如图中的(v3,v4),(v6,v5)权值均为9,选择哪一条在先均不影响最小生成树的生成结果。

    (3)选取n-1条恰当的边以连通n个顶点。

    • Prim算法代码

      #define INF 32767
      void Peim(MGraph g, int v)
      {
      	int lowcost[MAXV];
      	int min;
      	int closest[MAXV];
      	int i, j, k;
      	for (i = 0; i < g.n; i++)
      	{
      		lowcost[i] = g.edges[v][i];//置初值,放入顶点v和所有顶带你的权值
      		closest[i] = v;
      	}
      	for (i = 1; i < g.n; i++)//n-1条边,进行n-1次
      	{
      		min = INF;
      		for (j = 0; j < g.n; j++)//遍历找到权值最小的
      		{
      			if (lowcost[j] != 0 && lowcost[j] < min)
      			{
      				min = lowcost[j];
      				k = j;//记录下标
      			}
      		}
      		lowcost[k] = 0;//lowcost为0表示该顶点已使用
      		for (j = 0; i < g.n; j++)//遍历所有顶点,比较找到的顶点与其他顶点的权值是否比原来小
      		{
      			if (lowcsost[j] != 0 && g.edges[k][j] < lowcost[j])
      			{
      				lowcost[j] = g.edges[k][j];
      				closest[j] = k;//改变权值和相邻的顶点
      			}
      		}
      	}
      }
      
    • Prim算法的两个辅助数组是:closest和lowcost。

    • 时间复杂度为O(n^2),prim算法适合稠密图。

    1.3.2 Kruskal算法求解最小生成树

    • 实现Kruskal算法的辅助数据结构是什么?其作用是什么?Kruskal算法代码。

      void Kruskal(AdjGraph *g)
      {
       int i,j,u1,v1,sn1,sn2,k;
       int vest[MAXV];//集合辅组数组
       Edge E[MaxSize];//存放所有边
       k=0;//E数组的下标从0开始计
       for(i=0;i<g.n;i++)
         {
            p=g->adjlist[i].firstarc;
            while(p!=NULL)
            {
              E[k].u=i;E[k].v=p->adjlist;
              E[k].w=p->weight;
              k++;p=p->nextarc;
             }
          InsertSort(E,g.e);
          for(i=0;i<g.n;i++)
              vest[i]=i;
          k=1;
          j=0;
          while(k<g.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++;
                 for(i=0;i<g.n;i++)
                    if(vest[i]==sn2)
                       vest[i]=sn1;
                }
               j++;
            }
      }
      
    • 分析Kruskal算法时间复杂度,适用什么图结构,为什么?时间复杂的为O(elog2e),由于其与n无关,只与e有关,适用于稀疏图。

    1.4 最短路径

    有两种最短路径,一种是一个顶点点到其余各顶点之间的最短路径,另一种是任意两点之间的最小路径,分别用Dijkstra算法和Floyd算法来求解

    1.4 最短路径

    1.4.1 Dijkstra算法求解最短路径

    • Dijkstra算法需要哪些辅助数据结构
      借助最小索引堆作为辅助数据结构
    • Dijkstra算法如何解决贪心算法无法求最优解问题?展示算法中解决的代码
    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;
    if(g.edges[v][I]<INF)
    path[I]=v;
    else
    path[I]=-1;
    
    }
    S[v]=1;path[v]=0;
    for(I=0;i<g.n-1;i++)
    {
    MINdis=INF;
    for(j=0;j<g.n;j++)
    if(S[j]==0&&dist[j]<MINdis)
    {
    u=j;
    MINdis=dist[j];
    }
    S[u]=1;
    for(j=0;j<g.n;j++)
    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(g,dist,path,S,v);
    }
    
    • 输出单源最短路径的Dispath()函数如下:
    void Dispath(MatGraph g;int dist[],int path[],int S[],int v)
    {
    int I,j,k;
    int apath[MAXV],d;
    for(I=0;i<g.n;i++)
    if(S[I]==1&&I!=v)
    {
    printf("从顶点%d到顶点%d的路径长度为:%d	路径为:",v,i,dist[I]);
    d=0;apath[d]=I;
    k=path[I];
    if(k==-1)printf("无路径
    ");
    else
    {
    while(k!=v)
    {
    d++;apath[d]=k;
    k=path[k];
    }
    d++;apath[d]=v;
    printf("%d",apath[d]);
    for(j=d-1;j>=0;j--)
    printf(",%d",apath[j]);
    printf("
    ");
    }
    }
    }
    

    4、Dijkstra算法的时间复杂度
    Dijkstra算法的时间复杂度为o(n^2),其中n为图中的顶点数。

    1.4.2 Floyd算法求解最短路径

    • Floyed算法

      Floyd算法求解最短路径是每个顶点之间的,也可以Dijkstra调用n次,可能达到求解得出每个顶点之间的最短路径。两个时间的复杂性都是O(n^3),不过Floyd形式上更简单点。

      Floyd用一个二维数组A存放当前顶点最短路径长度,如:A[i][j]代表低你干点i到j之间的最短路径。
      还有一个path二维数组,和之前Dijkstra算法中的path的用途一致,用来回溯寻找路径进过的顶点,Floyd中的是每个顶点的集合成二维数组。

    • Floyd算法需要哪些辅助数据结构

      二维数组A用于存放当前顶点之间的最短路径长度,即分量A[i][j]表示当前i->j的最短路径长度。

    • Floyd算法优势,举例说明

      Floyd算法的优势:它能一次求得任何两个节点之间的最短路径,而Dijkstra算法只能求得以特定节点开始的最短路径。

    最短路径算法还有其他算法,可以自行百度搜索,并和教材算法比较。

    • SPFA算法(Shortest Path Fast Algorithm的缩写)

      int SPFA(int s, int t) {
      
          int dist[maxn], inq[maxn];
      
          for(int i = 0; i < n; i ++ ) {
      
              dist[i] = inf, inq[i] = 0;
      
          }
      
          queue<int>que;
      
          que.push(s), inq[s] = 1, dist[s] = 0;
      
          while(!que.empty()) {
      
              int now = que.front();
      
              que.pop();
      
              inq[now] = 0;
      
              for(int i = first[now]; ~i; i = edge[i].next) { //每次拿出一个点开始松弛。
      
                  int to = edge[i].to, w = edge[i].w;
      
                  if(dist[to] > dist[now] + w) { //这个if看下面的图
      
                      dist[to] = dist[now] + w;
      
                      if(!inq[to]) { //松弛过的点dist变换了,可能影响其他的点。需要继续松弛
      
                          inq[to] = 1;
      
                          que.push(to);
      
                      }
      
                  }
      
              }
      
          }
      
          return dist[t] == inf ? -1 : dist[t];
      
      }
      
    • 与其他两种算法的比较:

      PFA也叫bellman ford的队列优化。但是bellman ford的复杂度比较高。SPFA的平均复杂度是O(n*log2n),复杂度不稳定,在稠密图(边多的图)跑的比dijkstra慢,稀疏图(边少的图)跑的比Dijkstra快。在完全图达到最坏的平方级复杂度。(引用博文

    1.5 拓扑排序

    • 找一个有向图,并求其对要的拓扑排序序列

    上有向图的一种拓扑序列1->2->4->3->5;

    • 实现拓扑排序代码,结构体如何设计?

      结构体定义

      typedef struct
      {
      Vertex data;
      int count;
      ArcNode *firstarc;
      }VNode;
      

      代码示例

       void TopSort(AdjGraph *G)	
      {      
              int i,j;
              int St[MAXV],top=-1;	
              ArcNode *p;
              for (i=0;i<G->n;i++)		
      	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++)	
      	 if (G->adjlist[i].count==0)
      	 {	
                  top++;
      	    St[top]=i;
      	 }
               while (top>-1)			
               {	  
                  i=St[top];top--;			
      	    printf("%d ",i);		
      	    p=G->adjlist[i].firstarc;		
      	    while (p!=NULL)	
      	    {      
                       j=p->adjvex;
      	         G->adjlist[j].count--;
      	         if (G->adjlist[j].count==0)	
      	         {      
                         top++;
      		   St[top]=j;
      	         }
      	         p=p->nextarc;		
      	    }
              }
      }
      

    1.6 关键路径

    • 什么叫AOE-网?

    在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,一个工程常被分为多个小的子工程,这些子工程被称为活动(Activity),在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,这样的图简称为AOE网。——百度百科

    • 什么是关键路径概念?

      路径长度:路径上各活动持续时间的总和(即路径上所有权之和)。
      完成工程的最短时间:从工程开始点(源点)到完成点(汇点)的最长路径称为完成工程的最短时间。

      关键路径:路径长度最长的路径称为关键路径。

    • 什么是关键活动?

    关键活动:在关键路径上的活动称为关键活动。

    2.PTA实验作业(4分)

    2.1 六度空间(2分)

    2.1.1 伪代码(贴代码,本题0分)

     while (队不为空)
      {
           出队顶点V
           遍历V的所有邻接点
           {
               找到没有遍历过的顶点,进队列
               sum++;
           }
    
           如果在遍历的时候发现遍历的节点是这一层的最后一个
           { 
               level++;
               更新这层的最后一个
           }
               如果层数为6 //说明已经到了六层,剩下的就是不符合六度空间理论的顶点
               返回我们记录下的sum
      }
    
    

    2.1.2 提交列表

    2.1.3 本题知识点

    2.2 村村通(2分)

    2.2.1 伪代码(贴代码,本题0分)

    Prim算法
     {
    
        初始化lowcost,closest数组
        for(遍历lowcost数组) 
            if(lowcost[i]!=0 找最下边邻接点node 若权值不为 0 且小于 min)
            计算总费用
            end if
         检查新加入生成树的顶点和其他顶点是否存在更小的边
            if(lowcost[i]!=0&& edges[i][node]<=lowcost[node])
               lowcost[node]=edges[i][node]
            end if
        end for
        if(是否连通)
        for i=1 to g->n
        end if
     }
    

    2.2.2 提交列表

    2.2.3 本题知识点

  • 相关阅读:
    使用window.showModalDialog的一个问题
    ASP.NET 的<system.codedom>标签
    SqlServer事务中的可重复读和序列化隔离界别
    C#使用163的SMTP服务器发送邮件
    在HTML中小心使用空格和回车
    ASP.NET 4.0的ClientIDMode属性
    C/C++中判断某一文件或目录是否存在<转>
    新公司工作有感
    网络编程(转)<下>
    VC控件ListControl的使用
  • 原文地址:https://www.cnblogs.com/yqr2012/p/14802550.html
Copyright © 2011-2022 走看看