目录

0.PTA得分截图

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

1.1 图的存储结构

1.1.1 邻接矩阵

造一个图,展示其对应邻接矩阵

邻接矩阵的结构体定义

typedef struct{
  VextexType vexs[MaxVertexNum] //顶点表
  EdeType edges[MaxVertexNum][MaxVertexNum];//邻接矩阵,可看作边表
  int n,e; //图中当前的顶点数和边数
}MGragh;

建图函数

void CreateMGraph(MGraph* G)
{//建立无向网的邻接矩阵表示
	int i,j,k,w;
	scanf("%d%d", & G->n, & G->e); //输入顶点数和边数
	for (i = 0; i < n; i++) //读入顶点信息,建立顶点表
	{
		G->vexs = getchar();
	}
	for (i = 0; i < G->n; i++)
	{
		for (j = 0; j < G->n; j++)
		{
			G->edges[i][j] = 0; //邻接矩阵初始化
		}
	}
	for (k = 0; k < G->e; k++)
	{//读入e条边,建立邻接矩阵
		scanf("%d%d%d", & i, & j, & w); //输入边(v i ,v j )上的权w
		G->edges[i][j] = w;
		G->edges[j][i] = w;
	}
}//CreateMGraph

1.1.2 邻接表

造一个图,展示其对应邻接表(不用PPT上的图)

邻接矩阵的结构体定义

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 CreateALGraph(AdjGraph*& G)
{
	int n, e;
	cin >> n >> e;
	G->n = n, G->e = e;
	int i, x, y;
	ArcNode* p;
	G = new AdjGraph;
	for (i = 1; i <= n; i++)
		G->adjlist[i].firstarc = NULL;//头结点初始化

	for (i = 1; i <= e; i++)
	{
		cin >> x >> y;
		p = new ArcNode;
		p->adjvex = y;
		p->nextarc = G->adjlist[x].firstarc;//头插法
		G->adjlist[x].firstarc = p;

		p = new ArcNode;
		p->adjvex = x;
		p->nextarc = G->adjlist[y].firstarc;//无向图
		G->adjlist[y].firstarc = p;

	}

}

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

稀疏图适合用邻边表,稠密图适合用邻接矩阵。

且对于需要直接访问下标来访问结点,以便于更高效的比较的,需要使用邻接矩阵。
邻接矩阵的时间复杂度为:O(n^2),
邻接表的时间复杂度为: O(n+e)。

1.2 图遍历

1.2.1 深度优先遍历

选上述的图,继续介绍深度优先遍历结果

从v1开始,深度优先遍历的结果为:1 2 3 5 4

深度遍历代码

  • 邻接矩阵
void DFSGraph(Graph G) 
{  
 // 对图G作深度优先遍历。
   int v;
   for (v=1; v<G.vexnum; v++) 
     visited[v] = 0; // 访问标志数组初始化
   for (v=1; v<G.vexnum; v++) 
     if (!visited[v])
       DFS(G, v);         // 对尚未访问的顶点调用DFS
}
void DFS(Graph G, int v) {  
   // 从第v个顶点出发递归地深度优先遍历图G。
   visited[v] = 1;   
   cout<<v;      // 访问第v个顶点
   for(int i=1;i<=G.v;i++)
   {
      if(G.edges[v][i] == 1&& visited[i] == 0)//未访问结点
        DFS(G,i);
   }
}
  • 邻接表
void DFSGraph(AdjGraph G) 
{  
 // 对图G作深度优先遍历。
   int v;
   for (v=1; v<G.vexnum; v++) 
     visited[v] = 0; // 访问标志数组初始化
   for (v=1; v<G.vexnum; v++) 
     if (!visited[v])
       DFS(G, v);         // 对尚未访问的顶点调用DFS
}
void DFS(AdjGraph G, int v) {  
   // 从第v个顶点出发递归地深度优先遍历图G。
   visited[v] = 1;   
   cout<<v;      // 访问第v个顶点
   ArcNode *p;
   p = G->AdjGraph[v].firstarc;
   while(p != NUll)
   {
      if(visited[p->adjvex] == 0]
        DFS(G,p->adjvex);
      p = p->nextarc;

   }
}


深度遍历适用哪些问题的求解。(可百度搜索)

可用于求解路路径问题,求解一个点到另一个点的全部路径。

1.2.2 广度优先遍历

选上述的图,继续介绍广度优先遍历结果

从1开始,遍历的结果为,1 2 3 4 5,广度优先遍历,类似于树的层次遍历。

广度遍历代码

  • 邻接矩阵
void BFS(MGraph G,int v)
{
	int i, j;
	queue<MGrph>Q;
	cout << v;
	visited[v] = 1://标记已读
	q.push(v);
	while(!q.empty())//队列不为空	
	{
		v = q.front;//利用队列,实现类似于树的层次遍历来输出图的广度优先遍历序列
		q.pop();
		for (j = 0; j < G.n; j++)
		{
			if (G.edge[i][j] == 1 && !visited[j])
			{
				visited[j] = 1;
				cout << ',' << j;
				p.push(j);
			}
		}	
	}
}

  • 邻接表
void BFS(AdjGraph G,int v)
{
	int i, j;
	ArcNode* p;
	queue<int>Q;
	cout << v;
	visited[v] = 1://标记已读
	q.push(v);
	while(!q.empty())//队列不为空	
	{
		i = q.front();
		q.pop();
		p = G->adjlistp[i].firstarc;
		while (p)//遍历该结点的一条链
		{
			if (visited[p->adjvex] == 0)
			{
				visited[p->adjvex] = 1;
				cout << ',' << p->adjvex;
				q.psuh(p->adjvex);
			}

			p = p->nextarc;
		}
	}
}

广度遍历适用哪些问题的求解。(可百度搜索)

1、广度优先生成树
在广度优先遍历中,如果将每次“前进”(纵深)路过的(将被访问的)结点和边都记录下来,就得到一个子图,该子图为以出发点为根的树,称为广度优先生成树。这种情况与深度优先遍历类似。
类似地,也可以给广度优先生成树结点定义时间戳。
2、最短路径
显然,从v0出发广度优先遍历图,将得到v0到它的各个可达到的路径。我们这里定义路径上的边的数目为路径长度。与深度优先遍历不同,广度优先遍历得到的v0到各点的路径是最短路径(未考虑边权)。

1.3 最小生成树

用自己语言描述什么是最小生成树。

最小生成树是一副连通加权无向图中一棵权值最小的生成树,即将n个结点用n-1条边连接起来,边的权值的总和最小。

1.3.1 Prim算法求最小生成树

基于上述图结构求Prim算法生成的最小生成树的边序列

实现Prim算法的2个辅助数组是什么?其作用是什么?Prim算法代码。

一个辅助数组为lowcost,存放结点1到不同边的最小值,另一个数组为closest,存放最小边的前驱结点。

Prim代码

void Prim(Graph* G)
{
	int lowcost[1020];
	int MIN, i, j, k, sum = 0;
	for (i = 2; i <= G->v ; i++)
	{
		lowcost[i] = G->a[1][i];
	}
	lowcost[1] = 0;//初始化

	for(i = 1;i<G->v;i++)
	{
		k = -1;
		MIN = INF;
		for (j = 1; j <= G->v; j++)
		{
			if (lowcost[j] != 0 && lowcost[j] < MIN)
			{
				MIN = lowcost[j];
				k = j;//记录最近顶点编号
			}
		}

		if (k == -1)//找不到最小
		{
			return -1;
		}
			

		lowcost[k] = 0;

		for (j = 1; j <= G->v; j++)
		{
			if (lowcost[j] != 0 && G->a[k][j] < lowcost[j]&&G->a[k][j]>0)
			{
				lowcost[j] = G->a[k][j];
			}
		}
	}
}

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

时间复杂度为O(n^2),由于需与每一个相对的结点对比,多次调用权值,且为按结点顺序的遍历,
所以用邻接矩阵较为合适。

1.3.2 Kruskal算法求解最小生成树

基于上述图结构求Kruskal算法生成的最小生成树的边序列

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

辅助数组vset用于记录一个顶点所在的连通分量编号,判断是否为同一个连通图的集合,以此来判断是否会构成回路。

Kruskal代码

typedef struct
{
	int u;//起始顶点
	int v;//终止顶点
	int w;//权值
};

void Kruskal(MarGraph g)
{
	int i, j, ul, vl, sn1, sn2, k;
	int vest[MAXV];
	Edge E[MaxSize];//存放图中所有边
	k = 0;//e数组下标从0开始
	for (i = 0; i <= g.n; i++)//g产生边集E,不重复选取同一条边
		for (j = 0; j <= i; j++)
			if (g.edges[i][j] != 0 && e.edges[i][j] != INF)
			{
				E[k].u = i; E[k].v = j; E[k].w = g.edges[i][j];
				k++;
			}

	InserSort(E, g, e);//采用直接插入排序对E数组按权值进行排序
	for (i = 0; i < g.n; i++)//初始化vest数组
		vest[i] = i;

	k = 1;//当前构造生成树的第几条边
	j = 0;
	while (k < g.n)
	{
		ul = E[j].u; vl = E[j].v;//记录起始点和终止点
		sn1 = vest[ul];
		sn2 = vest[vl];
		if (sn1 != sn2)//为不同集合,该边是最小生成树的一条边
		{
			k++;
			for (i = 0; i < g.n; i++)
				if (vest[i] == sn2)//两个集合统一编号
					vest[i] = sn1;
		}
		j++;//扫描下一条边
	}
}

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

时间复杂度为(e^2),算法执行的时间仅与边数有关,与顶点数无关,所以适合用稀疏图,则适合用邻接表来存储图。

1.4 最短路径

1.4.1 Dijkstra算法求解最短路径

基于上述图结构,求解某个顶点到其他顶点最短路径。(结合dist数组、path数组求解

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

需要借助dist,path数组,其中dist用于存放顶点1到达对应顶点的最短路径,可以通过与数组中的值比较
不断更新最短路径,path保存到达该顶点最短路径的前驱顶点。
Dijkstra代码:

void Dijstra(Graph G, int i) {
	int k, j, pos, min;
	memset(visited, 0, sizeof(visit));     //初始化
	for (j = 0; j < G.V; j++)
		dist[j] = MAX_NUMBER;               //首先将距离都设置为无穷大
	j = i;
	dist[j] = 0;                 
	parent[j] = -1;            
	visit[j] = 1;             //首先将顶点i本身收录
	for (i = 1; i < G.V; i++) {
		for (k = 0; k < G.V; k++) {     //更新上次收录的顶点j对其他顶点的影响
			if (!visit[k] && dis[k] >= dis[j] + G.w[j][k]) 
			{
				dist[k] = dits[j] + G.w[j][k];
				parent[k] = j;
			}
		}
		pos = j, min = MAX_NUMBER;
		for (k = 0; k < G.V; k++) //修改路径
		{
			if (!visit[k] && min > dist[k])
			{
				pos = k;
				min = dist[k];
			}
		}
		j = pos;
		visit[j] = 1;  //将j收录
	}
}	}
}

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

在后面修改路径的判断条件中 用了min>dist[k]使仅有开始就判断出的最小的那条前置边。

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

时间复杂度为O(n^2),因为会不对调取边的权值,所以更适合用邻边矩阵。

1.4.2 Floyd算法求解最短路径

Floyd算法解决什么问题?

解决了最短路径的问题,可以求解某个顶点到各顶点的最短路径长度及输出路径。

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

需要A,path二维数组来存放顶点i到顶点j的路径关系,A存放最短的路径大小,path存放前驱。

Floyd算法优势,举例说明。

Floyd算法适合计算出各顶点到其他顶点的最短路径,相对于Dijkstra相当于在其只计算一个顶点到其他顶点的最短路径
的基础上加了一个循环,计算各个顶点。时间复杂度为O(n^3)。

1.5 拓扑排序

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


排序序列为
5, 7, 3, 11, 8, 2, 9, 10 (视觉上从左到右,从上到下)
3, 5, 7, 8, 11, 2, 9, 10 (最小编号的可用顶点优先)
5, 7, 3, 8, 11, 10, 9, 2 (最少边优先)
7, 5, 11, 3, 10, 8, 9, 2 (最大编号的可用顶点优先)
5, 7, 11, 2, 3, 8, 9, 10 (尝试从上到下,从左到右)
3, 7, 8, 5, 11, 10, 2, 9 (任意)

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

结构体

typedef struct {
	Vertex data;//顶点信息
	int count;//增加数据域:存放顶点入度
	AreNode *firstarc;//指向第一个邻接点
}VNode;

书写拓扑排序伪代码,介绍拓扑排序如何删除入度为0的结点?

伪代码

入度置初值为0
for 0 to n
  求所有顶点的入度
for 0to n
  将入度为0的顶点进栈
while 栈不空
  出栈,输出顶点
  找到邻接点并将入度-1
  将入度为0的结点入栈
End While

删除入度为0的结点,即将结点进栈。然后在弹出该结点时,把该结点的邻接点的入度-1。

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

是否you环路,可根据输出的顶点数量来判断,有环路,即不能将入度全部减成0然后输出,则会少输出结点。

1.6 关键路径

什么叫AOE-网?

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

什么是关键路径概念?

在项目管理中,关键路径是指网络终端元素的元素的序列,该序列具有最长的总工期并决定了整个项目的最短完成时间。
关键路径的工期决定了整个项目的工期。任何关键路径上的终端元素的延迟将直接影响项目的预期完成时间(例如在关键路径上没有浮动时间)。   一个项目可以有多个,并行的关键路径。另一个总工期比关键路径的总工期略少的一条并行路径被称为次关键路径。   最初,关键路径方法只考虑终端元素之间的逻辑依赖关系。关键链方法中增加了资源约束。   关键路径方法是由杜邦公司发明的。

什么是关键活动?

关键活动是为准时完成项目而必须按时完成的活动。即处于关键路径上的活动。

2.PTA实验作业(4分)

六度空间

2.1 六度空间(2分)

选一题,介绍伪代码,不要贴代码。请结合图形展开分析思路。


如图,1为开始的寻找关系的结点,设度为0,则与其有关系的绿色的结点为度为1的结点,再度为1的基础上,与其
相接的结点为度为2的结点,以此推论,类似于遍历的高度,所以可以用广度优先遍历来遍历一层对其进行求解。

伪代码为思路总结,不是简单翻译代码。

建立邻接矩阵
for 1 to n
  对每一个结点调用遍历的算法,
  计算其在六度内产生关联的结点数输出概率
End for

BFS

入队结点i
while 队列不为空
  出队
  for 1 to n//遍历顶点v的边
    if 未被遍历到
      入队
      统计在六度内的个数+1
      置下一个度的结点的最后一位为遍历到的j
    End if
  if  最后一个遍历到的顶点等于最近遍历的顶点时 
    度数+1
    更新当前度的最后一个结点
  if 度数为6
    结束

2.1.2 提交列表

2.1.3 本题知识点

主要运用了队列结构,对其层次进行遍历,以便于不漏掉每一个度的所包含的结点。
邻接矩阵的运用方便于调取权值运用。且通过last跟llast控制了这一层跟下一层的
队列里的层数的结点的最后一个是什么,以便于控制层数的递增。

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

村村通

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

伪代码为思路总结,不是简单翻译代码。

建立邻接矩阵
将每个点的权值置为∞
输入对应边

Prim

for 2 to v
  将lowcost数组初始化
End for

for 1 to v-1//找出n-1条最小边,生成最小树
  MIN置为∞
  for 1 to v
    if 存在更小边
    保存更小值,记录顶点编号
    end if  
  end for
  if 找不到最小边
    返回错误信息,结束函数
  end if  

  总值+=选取出来的边的权值
  边值置为0,即选取出来  

  for 1 to v/修改未选取出来的顶点
    if 存在更小值的边
      更改lowcost存放的值
  end for  
end for

2.2.2 提交列表

2.2.3 本题知识点

主要是对于最小生成树Prim算法的运用,使得每一个点都能构成最小的边的连接。

 posted on 2021-05-23 21:23    阅读(28)  评论(0编辑  收藏  举报