DS博客作业04--图

| 这个作业属于哪个班级 |

目录

| ---- | ---- | ---- |
| 这个作业的地址 |
目录

| 这个作业的目标 | 学习图结构设计及相关算法 |
| 姓名 | 吴慧敏 |
目录

🔅0.PTA得分截图

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

1.1 图的存储结构

1.1.1 邻接矩阵

邻接矩阵的图

  • 无向图的邻接矩阵

  • 有向图的邻接矩阵

邻接矩阵的结构体定义

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

邻接矩阵建图函数

void CreateMGraph(MGraph &g,int n,int e)
{  //n顶点个数,e边个数
     int i,j,a,b;
     for(i=0;i<n;i++)//从0开始
         for(j=0;j<n;j++)
              g.edges[i][j]=0;
     for(i=1;i<=e;i++)
     {
           cin>>a>>b;
           g.edges[a-1][b-1]=1;//因为二维数组从0开始,所以a,b需要减1
           g.edges[b-1][a-1]=1;
     }
  g.n=n;
  g.e=e;
}

时间复杂度为:O(n²)。

1.1.2 邻接表

🔺注:
邻接表表示不唯一,特别适合于稀疏图的存储(邻接表的存储空间为O(n+e))。

  • 邻接表的结构体定义
//声明边结点类型
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;
    int a, b;
    ArcNode* p;
    G = new AdjGraph;
    //G->adjlist = new VNode[n];//申请空间
    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 = b;
        p->nextarc = G->adjlist[a].firstarc;
        G->adjlist[a].firstarc = p;
        //无向图,另一个节点也存在相同的关系
        p = new ArcNode;
        p->adjvex = a;
        p->nextarc = G->adjlist[b].firstarc;
        G->adjlist[b].firstarc = p;
    }
    G->e = e;
    G->n = n;
}

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

※邻接矩阵:适用数据量较大的图(类似稠密图),用一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。时间复杂度为O(n²)。
※邻接表:适用数据量较少的图(稀疏图),因为更节省空间。是一种数组与链表相结合的存储方法;时间复杂度为O(n+e)。

对于一个具有n个顶点e条边的无向图,它的邻接表表示有n个顶点表结点2e个边表结点,它的邻接表表示有n个顶点表结点e个边表结点

1.2 图遍历

1.2.1 深度优先遍历

  • 无向图的深度优先遍历

    从A开始遍历
    遍历结果:A -> B -> G -> E -> C -> D -> H -> F
    第1步:访问A。
    第2步:访问B(A的邻接点)。 在第1步访问A之后,接下来应该访问的是A的邻接点,即"B,D,F"中的一个。但在本文的实现中,顶点ABCDEFGH是按照顺序存储,B在"D和F"的前面,因此,先访问B。
    第3步:访问G(B的邻接点)。 和B相连只有"G"(A已经访问过了)
    第4步:访问E(G的邻接点)。 在第3步访问了B的邻接点G之后,接下来应该访问G的邻接点,即"E和H"中一个(B已经被访问过,就不算在内)。而由于E在H之前,先访问E。
    第5步:访问C(E的邻接点)。 和E相连只有"C"(G已经访问过了)。
    第6步:访问D(C的邻接点)。
    第7步:访问H。因为D没有未被访问的邻接点;因此,一直回溯到访问G的另一个邻接点H。
    第8步:访问(H的邻接点)F。

  • 有向图的深度优先遍历

    从A开始遍历
    遍历结果:A -> B -> F -> H -> G -> C -> D -> E
    第1步:访问A。
    第2步:访问(A的出度对应的字母)B。 在第1步访问A之后,接下来应该访问的是A的出度对应字母,即"B,C,F"中的一个。但在本文的实现中,顶点ABCDEFGH是按照顺序存储,B在"C和F"的前面,因此,先访问B。
    第3步:访问(B的出度对应的字母)F。 B的出度对应字母只有F。
    第4步:访问H(F的出度对应的字母)。 F的出度对应字母只有H。
    第5步:访问(H的出度对应的字母)G。
    第6步:访问(G的出度对应字母)E。 在第5步访问G之后,接下来应该访问的是G的出度对应字母,即"B,C,E"中的一个。但在本文的实现中,顶点B已经访问了,由于C在E前面,所以先访问C。
    第7步:访问(C的出度对应的字母)D。
    第8步:访问(C的出度对应字母)D。 在第7步访问C之后,接下来应该访问的是C的出度对应字母,即"B,D"中的一个。但在本文的实现中,顶点B已经访问了,所以访问D。
    第9步:访问E。D无出度,所以一直回溯到G对应的另一个出度E。

深度遍历代码

  • 邻接矩阵
void DFS(MGraph g, int v)//邻接矩阵深度遍历 
{
   
    if (flag == 0)
    {
        cout << v;
        flag = 1;
    }
    else
        cout << " " << v;   //输出顶点
    visited[v] = 1;//标记已访问该节点
    for (int i = 1; i <= g.n; i++)
    {
        if(g.edges[v][i] == 1 && visited[i] == 0)
        {
            DFS(g, i); //当前顶点与 i 顶点邻接且未被访问,递归搜索
        }
    }
}

  • 邻接表
void DFS(AdjGraph* G, int v)//v节点开始深度遍历 
{
    ArcNode* p;
    p = G->adjlist[v].firstarc;

    if (flag == 0)
    {
        cout << v;
        flag = 1;
    }
    else
        cout << " " << v;
    visited[v] = 1;  //标记已访问

    while (p)
    {
        if (!visited[p->adjvex])//未被访问过
            DFS(G, p->adjvex);
        p = p->nextarc;
    }
}

深度遍历适用哪些问题的求解。

深度遍历适用于解决访问初始点v后再访问与定点v相邻的顶点w,再以w为初始点去访问w的相邻点。可以找到两点之间的全部路径。例如迷宫、六度空间、全排列问题等。
eg.全排列问题:也就是深度遍历的思想,首先我们去第一个,然后访问下一个,然后再接下来一个。图解

1.2.2 广度优先遍历

注:其主要思想类似于树的层序遍历。

无向图的广度优先遍历

遍历顺序是:A -> B -> C -> D -> F -> G -> E -> H
从A开始,有4个邻接点,“B,C,D,F”,这是第二层;
在分别从B,C,D,F开始找他们的邻接点,为第三层。以此类推。

有向图的广度优先遍历


从A开始
遍历序列是:A -> B -> C -> F -> D -> H -> G-> E

广度遍历代码

  • 邻接表
void BFS(AdjGraph* G, int v) //v节点开始广度遍历  
{
    queue<int>q;
    ArcNode* node;
    int n;//边的序号
    int j;
    visited[v] = 1;//表示已访问
    cout << v ;
    q.push(v);//入队

    while (!q.empty())//队不空
    {
        j = q.front();
        q.pop();
        node = G->adjlist[j].firstarc;
        while (node)//按邻接表输出头结点后的所有节点
        {
            if (!visited[node->adjvex])
            {
                visited[node->adjvex] = 1;
                cout << " " << node->adjvex;
                q.push(node->adjvex);
            }
            node = node->nextarc;
        }
    }
}
  • 邻接矩阵
void BFS(MGraph g, int v)//邻接矩阵广度遍历 
{
    int t;
    queue<int>q;
    if (visited[v] == 0)
    {
        cout << v;
        visited[v] = 1;
        q.push(v);
    }
    while (!q.empty())
    {
        t = q.front();
        q.pop();
        for (int j = 1; j <= g.n; j++)
        {
            if (g.edges[t][j] == 1 && visited[j] == 0)
            {
                cout << " " << j;
                visited[j] = 1;
                q.push(j);
            }
        }
    }
}

广度遍历适用哪些问题的求解。

广度遍历适用于先访问初始点v,接着访问顶点v的所有未被访问过的邻接点v1,v2...vt,然后再按v1,v2...vt的次序访问每一个顶点的所有未被访问过的邻接点。
可用于求解迷宫问题的最短路径。也可用于求不带权无向连通图中的两个顶点的最短路径、求不带权无向连通图中距离一个顶点v的最远顶点等等。

1.3 最小生成树

  • 什么是最小生成树?
    首先要知道,对于带权连通图G(每条边上的权均为大于零的实数),可能有多棵不同生成树。每棵生成树的所有边的权值之和可能不同,其中权值之和最小的生成树称为图的最小生成树
    最小生成树的顶点个数和其原来的图的顶点个数相等,边数为(n-1)。
    最小生成树可以用于解决公路村村通的问题,使得在各村均连通的情况下所用的费用最少。

1.3.1 Prim算法求最小生成树

  • Prim算法思想:
    逐渐长成一棵最小生成树。假设G=(V,E)是连通无向网,T=(V,TE)是求得的G的最小生成树中边的集合,U是求得的G的最小生成树所含的顶点集。初态时,任取一个顶点u加入U,使得U={u},TE=Ø。重复下述操作:找出U和V-U之间的一条最小权的边(u,v),将v并入U,即U=U∪{v},边(u,v)并入集合TE,即TE=TE∪{(u,v)}。直到V=U为止。最后得到的T=(V,TE)就是G的一棵最小生成树。也就是说,用Prim求最小生成树是从一个顶点开始,每次加入一条最小权的边和对应的顶点,逐渐扩张生成的。

对应生成的最小生成树的边序列为:

实现Prim算法的2个辅助数组

①closest[i]:最小生成树的边依附在U中顶点编号。
②lowcost[i]:表示顶点i(∈V-U)到U中顶点的边权重,取最小权重的顶点k加入U。并规定lowcost[k]=0表示这个顶点在U中
(closest[k],k)构造最小生成树一条边。

  • Prim算法代码
#define INF 32767//INF表示∞
void  Prim(Graph G,int v)
{     
      int lowcost[MAXV];
      int min;
      int 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记录最近顶点编号
	     }
	     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;
	     }
        }
}

分析Prim算法时间复杂度,适用什么图结构,为什么?

  • Prim算法时间复杂度为O(n²);
  • Prim算法适用于邻接矩阵,因为要调用到权值,找到特定顶点间的权值。适用于边数较多的稠密图,其是通过比较边来找顶点,每次遍历找到一个顶点,与顶点个数无关。

1.3.2 Kruskal算法求解最小生成树

  • 基本思想:
    将所有顶点之间路径权值,从小到大有序排列,并在程序中优先遍历路径小的顶点,记录不存在环的路径,直至全部循环完毕。
  • 关键:
    1、将输入的边的顺序按边的权值从小到大排序。
    2、在遍历的过程中判断是否生成环(难点)。
    最小生成树的边序列如图:

Q:实现Kruskal算法的辅助数据结构是什么?其作用是什么?

数组vest[MAXV],为集合辅助数组,用于记录起始点和终止点的下标,通过改变数组的值来改变顶点的所属集合。

  • Kruskal算法代码。
typedef struct 
{    int u;     //边的起始顶点
     int v;      //边的终止顶点
     int w;     //边的权值
} Edge; 
Edge E[MAXV];
void Kruskal(Graph 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
	for (j=0;j<G.n;j++)
	      if (G.edges[i][j]!=0 && G.edges[i][j]!=INF)
	      {     
                     E[k].u=i;  E[k].v=j;  E[k].w=G.edges[i][j];
	             k++;
	      }
        InsertSort(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)  	//两顶点属于不同的集合
	      {
                k++;		   	//生成边数增1
		for (i=0;i<g.n;i++)  	//两个集合统一编号
		    if (vset[i]==sn2) 	//集合编号为sn2的改为sn1
		       vset[i]=sn1;
	      }
	     j++;			   //扫描下一条边
        }
}
  • 并查集改良Kruskal算法(时间复杂度是O(elog2e)):
typedef struct 
{    int u;     //边的起始顶点
     int v;      //边的终止顶点
     int w;     //边的权值
} Edge; 
Edge E[MAXV];
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\n",u1,v1,E[j].w);
		k++;		   	//生成边数增1
		for (i=0;i<g.n;i++)  	//两个集合统一编号
		      if (vset[i]==sn2) 	//集合编号为sn2的改为sn1
			vset[i]=sn1;
	     }
	     j++;			   //扫描下一条边
            }
}

分析Kruskal算法时间复杂度,适用什么图结构,为什么?

  • Kruskal算法时间复杂度:O(elog₂e)。
  • 适用于邻接表,因为其与n无关,只与e有关,适用于稀疏图。用邻接表的遍历方便找到两点之间的权值,也更加方便找到某点与其连通图的关系并比较权值。

1.4 最短路径

1.4.1 Dijkstra算法求解最短路径

  • Dijkstra算法伪代码
初始化dist数组,path数组,s数组;
遍历图中所有结点
{
   遍历dist数组,找为被s收入的距离源点最小的顶点w;
   s[w]=1;//将w加入集合s;
   for i=0 to g.n //修正未加入s集合的顶点的dist和path
       若dist[i]>dist[w]+g.edges[w][i];
          dist[i]=dist[w]+g.edges[w][i];
          path[i]=w;
   end for
}
  • Dijkstra算法代码:
void Dijkstra(MatGraph g, int v)
{
	int dist[MAXV],path[MAXV];
	int s[MAXV];//s[i]=1表示顶点i在s中,s[i]=0表示顶点i在U中,即判断是否访问
	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有边时,置顶点i的前一个顶点为v
		else
			path[i] = -1;//顶点v到顶点i没边时,置顶点i的前一个顶点为-1
	}
	s[v] = 1;path[v]=0;//源点编号v放入s中
	for (i = 0; i < g.n-1; i++)//循环直到所有顶点的最短路径都求出
	{
		MINdis = INF;//MINdis置最大长度初值
		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++)//修改改变结点后的路径长度
		{
			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(g,dist,path,s,v);//输出最短路径
}

所画图为:

  • 注:
    ①dist[]数组用于存放最短路径长度,通过改变path数组的值改变顶点的前继结点。例如dist[2]=12表示源点V0➡顶点2的最短路径长度为12。
    ②path[]数组用于存放最短路径。最短路径序列的前一个顶点的序号,初值或无路径用-1表示。例如从顶点0➡5的最短路径为0,2,3,5,表示为path[5]={0,2,3,5}。从源点到其他顶点的最短路径有n-1条,二维数组path[] []存储。

Dijkstra算法如何解决贪心算法无法求最优解问题?展示算法中解决的代码。

贪心算法无法求最优解问题,在该代码中dist[u]+g.edges[u][j]<dist[j]此处的小于号将该题的解法固定唯一,若改为<=号则会出现多种解法。

Dijkstra算法的时间复杂度,使用什么图结构,为什么。

1.时间复杂度为O(n²);
2.使用邻接矩阵存储。因为邻接矩阵方便通过下标找到顶点间权值并在数组中改变,若为邻接表则较难找到权值。

1.4.2 Floyd算法求解最短路径

  • 伪代码
初始化二维数组A,二维数组path;
for k=0 to g.n
   遍历二维数组A
     将顶点k作为中间站,判断加入顶点k后的路径长度是否比原来小;
     若 A[i][j]>A[i][k]+A[k][j]
         修改A[i][j]=A[i][k]+A[k][j];
         修改path[i][j]=k;
end for
  • 具体算法代码:
void Floyd(MatGraph g)//Floyd算法
{
	int A[MAXV][MAXV], path[MAXV][MAXV];
	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][i] = i;//顶点i到j有边时
	else
		path[i][i] = -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[j][k] + A[k][i])
					{
						A[D[] = A[i][K] + A[k][i]; //修改最短路径长度
						path[i][j] = path[K][i];	   //修改最短路径
					}
		}
	}
        Dispath(g,A,path);//输出最短路径
}
  • Q1:Floyd算法解决什么问题?
    求图的任两结点间的最短路径。
  • Q2:Floyd算法需要哪些辅助数据结构?
    Floyd需要A[][]和path[][]两个二维数组,其中A数组是用于存放两个顶点之间的最短路径,path数组用于存放其的前继结点。
  • Q3:Floyd算法优势?
    Floyd算法可以算出任意两个节点之间的最短距离,代码编写简单。Floyd算法适用于APSP(All Pairs Shortest Paths,多源最短路径),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法。
    但是Floyd算法时间复杂度比较高,不适合计算大量数据。

1.5 拓扑排序

  • 定义:在一个有向无环图中找一个拓扑序列的过程称为拓扑排序。
  • 注意:有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序。
  • 拓扑排序的应用:可以用来检测图中是否有回路。
  • 序列必需满足的条件:
    ①每个顶点出现且只出现一次;
    ②若存在一条从顶点A到顶点B的路径,那么在序列中顶点A出现在顶点B的前面。
    所画的有向图如下:

1.5.1上图对应的拓扑排序序列为:

序列1:1,5,2,3,6,4
序列2:5,1,2,6,3,4
序列3:5,1,2,3,6,4

易知:一个AOV-网的拓扑序列不是唯一的。

  • 如何进行拓扑排序?
    (拓扑排序每次找到入度为0为结点,每输出一个结点,其后续结点的入度减一,入度为0的结点进入栈或数组。)
    ⒈从有向图中选取一个没有前驱的顶点,并输出之;
    ⒉从有向图中删去顶点以及所有以它为尾的弧
    ⒊重复上述两步,直至图空,或者图不空但找不到无前驱的顶点为止。

1.5.2结构体设计(实现拓扑排序代码)

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

1.5.3拓扑排序伪代码

遍历领接表
    计算每个顶点的入度,存入头结点count成员
遍历图顶点
    若发现入度为0的顶点,入栈st
while(栈不空)
{
     出栈节点v,访问。
     遍历v的所有领接点
        {
              所有领接点的入度为-1
              若有领接点入度为0,则入栈st   
        }
}

注:
1.数组存放,插入删除操作比较复杂;
2.栈、队列保存,O(n+e)。

1.5.4如何用拓扑排序代码检查一个有向图是否有环路?

  • 检测是否有环的办法:
    使用邻接表;当某个顶点的入度为0时,输出顶点信息;设置栈来存放入度为0的顶点。
    ⑴对有向图构造其顶点的拓扑有序序列
    ⑵若网中所有顶点都在其拓扑有序序列中,则该AOV-网必定不存在环
  • 具体代码:
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;		//找下一个邻接点
	    }
        }
}

1.6 关键路径

关键路径是从有向图的源点(入度为0)到汇点(出度为0)的最长路径

Q1:什么叫AOE-网?

AOE 网是在 AOV 网的基础上,其中每一个边都具有各自的权值,是一个有向无环网。其中权值表示活动持续的时间,用顶点表示事件,用有向边e表示活动,边的权c(e)表示活动持续时间。是带权的有向无环图。
⬜AOE-网图的意义:

  • AOE-网-----带权的有向无环图;
  • 顶点----事件或状态
  • 弧(有向边)---活动及发生的先后关系
  • 权----活动持续的时间
  • 起点----入度为0的顶点(只有一个)
  • 终点----初度为0的顶点(只有一个)

Q2:什么是关键路径概念?

关键路径是指有向图中从源点到汇点的最长路径。其中,关键路径中的边叫做关键活动。
求一个AOE的关键路径➡求AOE的中的关键活动。

求关键路径的步骤:

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.关键活动连接起来就是关键路径。

Q3:什么是关键活动?

指的是关键路径中的边。

🔅2.PTA实验作业(4分)

2.1 六度空间(2分)

2.1.1 伪代码

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<queue>
#define maxv 10001
定义矩阵;
int main()
{
        输入顶点数和边数
	for(i=1;i<=e;i++)
        {
               输入数据对矩阵的元素进行修改;
        }
	for (i = 1; i <= n; i++)
	{
                初始化各点,将visited置0
		node = BFS(i);
                所占百分比node/n;
                输出
	}
}
int BFS(int i)//广度优先遍历
{
        level//用于记录层数
        count//用于记录每个结点的与该结点距离不超过6的结点数
        last//用于标记每层最后一个结点,以便判断是否为该层最后一个
        int tail,temp;
	访问该结点并进队;
        将访问过的结点的visited[]置为1
	while (队不为空&&level小于6)
	{
                用temp保存队头元素
		弹出队头元素;
		for (遍历结点)
		{
			if (未访问过该结点且边存在)
			{
                                count++;
				访问进队;
                                置该结点的visited[]为1
			        tail记录此时结点;
			}
		}
		if (last == temp)//为该层最后一个
		{
			层数加1
			last = tail;//移动到下一层
		}
	}
        return count;  
}

2.1.2 提交列表

2.1.3 本题知识点

①需要借助队列来解决问题,类似于层次遍历
②需要借助数组visited[]来标记已经访问过的结点
③利用邻接矩阵进行广度遍历
④定义level,tail,last对层数进行判断,以及层数的改变

2.2 村村通或通信网络设计或旅游规划(2分)

村村通

2.2.1 伪代码

定义图的结构体;
void CreateMGraph(MatGraph*& g, int n, int e)//建图
{
       for(i=0;i<=n;i++)
       {
            for(j=0;j<n;j++)
                  对矩阵初始化;(不能初始化为0,要初始化为最大值IFN)
       }
       for(i=1;i<=e;i++)
       {
             修改矩阵数据;
       }
       g->e = e;
       g->n = n;
}
int Connect(MatGraph* g)
{
      int lowcost[1005];//保存权值
      int closet[];//保存顶点下标
      int cost = 0,minn=IFN,k;
      for(i=1;i<=g->n;i++)
      {
           给lowcost[i]置初值;
           cosest[i] 置为1; 
      }

      lowcost[1] = 0;

      for(i=2;i<=q->n;i++)
      {
            minn = IFN;
            for(j=1;j<=g->n;j++)
            {
                if(权值存在且权值小于minn)
                {
                       让minn=此时权值;
                       k=j;//记录最近顶点的编号;
                }
            }
            lowcost[k] = 0;//标记已经加入

            for (int j = 1; j <= g->n; j++)
            {
                 if (权值存在 && 权值 大于 此时指向的权值)
                 {
                     修正数组
                 }
            }
        }
        for (int i = 1; i <= g->n; i++) 
             判断是否全部连接,若还有lowcost[i],则返回-1
        if (lowcost[i])return -1;
   返回 cost;
}
int main()
{
	输入边数和顶点数;
	CreateMGraph(g,n,e);//建图
	if(数据不足以保证畅通)
              输出-1;
        else
               输出Connect(g);
         return 0;
}

2.2.2 提交列表

2.2.3 本题知识点

公路村村通实际上是求最小生成树的问题,即要求每个村庄之间必须可以连通但是长度要求最短。
①借助邻接矩阵进行存储数据
②借助Prim算法解决最小生成树的问题
③借助两个数组lowcost[]和closet[]分别保存权值和顶点下标。

posted @ 2021-05-23 19:14  蓝胖子WHM  阅读(47)  评论(1编辑  收藏  举报