第九篇博客
| 这个作业属于哪个班级 |
| ---- | ---- | ---- |
| 这个作业的地址 |
| 这个作业的目标 | 学习图结构设计及相关算法 |
| 姓名 | 卢伟杰 |
0.PTA得分截图
图题目集总得分,请截图,截图中必须有自己名字。题目至少完成2/3,否则本次作业最高分5分。
1.本周学习总结(6分)
本次所有总结内容,请务必自己造一个图(不在教材或PPT出现的图),围绕这个图展开分析。建议:Python画图展示。图的结构尽量复杂,以便后续可以做最短路径、最小生成树的分析。
1.1 图的存储结构
1.1.1 邻接矩阵
-
造一个图,展示其对应邻接矩阵
-
邻接矩阵的结构体定义
typedef struct
{
char vexs[MAXN];
int arc[MAXN][MAXN];
int numVertexes,numEdges;
}MGraph;
- 建图函数
void GreateMGraph(MGraph *G)
{
int i,j,k,w;
cout<<"请输入顶点数和边数:"<<endl;
scanf("%d%d",&G->numVertexes,&G->numEdges);
for(i=0;i<G->numVertexes;i++)
scanf(&G->vexs[i]);
for(i=0;i<G->numVertexes;i++)
for(j=0;j<G->numVertexes;j++)
G->arc[i][j]=INFINITY;
for(int k=0;k<G->numVertexes;k++)
{
cout<<"输入边(vi,vj)上的下标i,下标j和权w:"<<endl;
cin>>i>>j>>w;
G->arc[i][j]=w;
G->arc[j][i]=G->arc[i][j];
}
}
1.1.2 邻接表
-
造一个图,展示其对应邻接表
-
邻接矩阵的结构体定义
typedef struct EdgeNode
{
int adjvex;
EdgeType weight;
struct EdgeNode *next;
} EdgeNode;
typedef struct VextexNode
{
VertexType data;
EdgeNode *firstedge;
} VextexNode, AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
int numNodes, numEdges;
} GraphAdjList;
- 建图函数
void CreateALGraph(GraphAdjList *Gp)
{
int i, j, k;
EdgeNode *pe;
cout << "输入顶点数和边数(空格分隔):" << endl;
cin >> Gp->numNodes >> Gp->numEdges;
for (i = 0 ; i < Gp->numNodes; i++)
{
cout << "输入顶点信息:" << endl;
cin >> Gp->adjList[i].data;
Gp->adjList[i].firstedge = NULL;
}
for (k = 0; k < Gp->numEdges; k++)
{
cout << "输入边(vi,vj)的顶点序号i,j(空格分隔):" << endl;
cin >> i >> j;
pe = (EdgeNode *)malloc( sizeof(EdgeNode));
pe->adjvex = j;
pe->next = Gp->adjList[i].firstedge;
Gp->adjList[i].firstedge = pe;
pe = (EdgeNode *)malloc( sizeof(EdgeNode));
pe->adjvex = i;
pe->next = Gp->adjList[j].firstedge;
Gp->adjList[j].firstedge = pe;
}
}
1.1.3 邻接矩阵和邻接表表示图的区别
各个结构适用什么图?时间复杂度的区别。
-
采用邻接矩阵表示时,设邻接矩阵有n×n阶,矩阵包含n^2个元素。对每个顶点来说,
搜索其所有邻接点需要搜索矩阵中对应的整个一行,因此,对整个图的遍历来说,
需要搜索整个矩阵,算法的时间复杂度为O(n^2) -
采用邻接表表示时,若邻接表有n个结点和e条边,对每个顶点来说,搜索其所有邻接
点需要搜索邻接表中对应的链表的各结点,算法的时间复杂度为O(n+e)
1.2 图遍历
1.2.1 深度优先遍历
-
选上述的图,继续介绍深度优先遍历结果
-
v0, v1, v2, v3
-
深度遍历代码
void DFS(MGraph g, int v)
{
int i;
visited[v-1] = 1;
if (!flag)
{
cout << v;
flag = 1;
}
else
{
cout << " " << v;
}
for (i = 0; i < g.n; i++)
{
if (g.edges[v-1][i] == 1 && visited[i] == 0)
{
DFS(g, i + 1);
}
}
}
-
深度遍历适用哪些问题的求解。(可百度搜索)
-
用DFS(深度优先遍历)解决可达性/连通性问题
private int m, n;
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
m = grid.length;
n = grid[0].length;
int islandsNum = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] != '0') {
dfs(grid, i, j);
islandsNum++;
}
}
}
return islandsNum;
}
private void dfs(char[][] grid, int i, int j) {
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') {
return;
}
grid[i][j] = '0';
for (int[] d : direction) {
dfs(grid, i + d[0], j + d[1]);
}
}
1.2.2 广度优先遍历
-
选上述的图,继续介绍广度优先遍历结果
-
v0, v1, v3, v2
-
广度遍历代码
void BFS(MGraph g, int v)
{
int i, k;
int cur_node;
int queue[MAXV];
int front, rear;
front = rear = 0;
visited[0] = 0;
visited[v - 1] = 1;
queue[rear++] = v;//enqueue
cout << v;
while (front != rear)
{
cur_node = queue[front++];
for (i = 0; i < g.n; i++)
{
if (visited[i] == 0 && g.edges[cur_node-1][i] == 1)
{
cout << " " << i + 1;
queue[rear++] = i + 1;
visited[i] = 1;
}
}
}
}
-
广度遍历适用哪些问题的求解。(可百度搜索)
-
通常用于求解无向图的最短路径问题
1.3 最小生成树
用自己语言描述什么是最小生成树。
- 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含
原图中的所有 n 个结点,并且有保持图连通的最少的边。
1.3.1 Prim算法求最小生成树
-
实现Prim算法的2个辅助数组是什么?其作用是什么?Prim算法代码。
-
两个数组数组closest和lowcost,分别记录V-U顶点j到U中顶点的最小边。
void Prim(Graph G)
{
for(int i=0;i<G.vertexNum;i++)
{
lowcost[i]=G.arc[0][i];adjvex[i]=0;
}
lowcost[0]=0;
for(int i=1;i<G.vertexNum;i++)
{
k=MinEdge(lowcost,G.vertextNum);
cout<<k<<adjvex[k]<<lowcost[k];
lowcost[k]=0;
for(int j=1;j<G.vertexNum;j++)
{
if(G.arc[k][j]<lowcost[j])
{
lowcost[j]=G.arc[k][j];
adjvex[j]=k;
}
}
}
}
-
分析Prim算法时间复杂度,适用什么图结构,为什么?
-
Prim算法的时间复杂度为O(n^2),适用于稠密图的最小生成树,两重for循环
1.3.2 Kruskal算法求解最小生成树
-
实现Kruskal算法的辅助数据结构是什么?其作用是什么?Kruskal算法代码。
-
vset[i]用来记录一个顶点i所在的连通分量编号
void Kruskal(MGraph *G)
{
Edge EdgeCount[10];
int vset[MAX_V];
int k = 0;
int m = G->v;
int i,j,s1,s2;
for(i = 0; i < G->v; i++)
{
for(j = 0; j < G->v; j++)
{
if(G->matrix[i][j] != 0)
{
if(i < j)
{
EdgeCount[k].v1 = i;
EdgeCount[k].v2 = j;
EdgeCount[k].weight = G->weight[i][j];
k++;
}
}
}
}
Sort(EdgeCount, G);
for (i=0;i<G->v;i++)
{
vset[i]=i;
}
i = 0;
while(m > 1)
{
s1 = vset[EdgeCount[i].v1];
s2 = vset[EdgeCount[i].v2];
if(s1 != s2)
{
cout << EdgeCount[i].v1 << "--" << EdgeCount[i].v2 << endl;
m--;
for(j = 0; j < G->v; j++)
{
if(vset[j] == s2)
vset[j] = s1;
}
}
i++;
}
}
-
分析Kruskal算法时间复杂度,适用什么图结构,为什么?
-
Kruskal算法的时间复杂度为O(e*loge) e为边数。克鲁斯卡尔算法主要针对边展开,
边数少时效率会很高,所以对于稀疏图有优势。这个复杂度就是快排需要的时间
1.4 最短路径
1.4.1 Dijkstra算法求解最短路径
- 基于上述图结构,求解某个顶点到其他顶点最短路径。(结合dist数组、path数组求解)
-
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;
}
}
}
-
Dijkstra算法的时间复杂度,使用什么图结构,为什么。
-
Dijkstra算法时间复杂度为O(n^2),邻接矩阵
1.4.2 Floyd算法求解最短路径
-
Floyd算法解决什么问题?
-
适用于APSP(All Pairs Shortest Paths,多源最短路径),是一种动态规划算法,稠密图效果最佳,边权可正可负
-
Floyd算法需要哪些辅助数据结构
-
二维数组用于存放当前顶点之间的最短路径长度
-
Floyd算法优势,举例说明。
-
此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法
最短路径算法还有其他算法,可以自行百度搜索,并和教材算法比较。
1.5 拓扑排序
-
找一个有向图,并求其对要的拓扑排序序列
-
3, 1, 4,2, 6, 5
-
实现拓扑排序代码,结构体如何设计?
-
拓扑排序结构体
typedef struct
{
Vertex data;
int count;
ArcNode *firstarc;
}VNode;
- 书写拓扑排序伪代码,介绍拓扑排序如何删除入度为0的结点?
void TopSort(Graph g)
{
for (int i=0; i<vertexnum; i++)
{
vertex v = FindZeroIndegree(g);
if (v is not vertex)
{
cout <<"the graph has cycle"<<endl;
}
cout << v ;
foreach vertex w adjacent to v
w.indegree--;
}
}
- 如何用拓扑排序代码检查一个有向图是否有环路?
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-网?
-
在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间
-
什么是关键路径概念?
-
关键路径是指设计中从输入到输出经过的延时最长的逻辑路径。优化关键路径是一种提高
设计工作速度的有效方法。一般地,从输入到输出的延时取决于信号所经过的延时最大路径,
而与其他延时小的路径无关。在优化设计过程中关键路径法可以反复使用,直到不可能减少
关键路径延时为止。EDA工具中综合器及设计分析器通常都提供关键路径的信息以便设计者
改进设计,提高速度。 -
什么是关键活动?
-
关键活动是为准时完成项目而必须按时完成的活动。即处于关键路径上的活动。所有项目都是
由一系列活动组成,而在这些活动中存在各种链接关系和活动约束。其中有些活动如果延误就
会影响整个项目工期。在项目中总存在这样一类直接影响项目工期变化的活动
2.PTA实验作业(4分)
2.1 六度空间(2分)
选一题,介绍伪代码,不要贴代码。请结合图形展开分析思路。
2.1.1 伪代码(贴代码,本题0分)
while(队列不空)
temp=队首的元素
队首元素出队并且将其改为已访问
while(p不为空)
if(p->adjvex未被访问)
该结点进队并标记为已访问
cnt加一;
tail记录此时结点的值
end while
if(队首等于该层最后一个结点)
层数加一,last重置为队尾元素
if(层数为6)
结束
返回标记的结点个数
伪代码为思路总结,不是简单翻译代码。
2.1.2 本题知识点
- 邻接矩阵、辅助结构为二维数组,再用BFS遍历
2.2 村村通或通信网络设计或旅游规划(2分)
2.2.1 伪代码(贴代码,本题0分)
Prim函数
{
int cnt=1,cost=0, v;
for i=1 to N
parent[i]等于s;
dist[i] 等于c[i];
parent[s]等于-1;
dist[s]等于0;
while
v=findmin();
if(v==-1)结束循环;
cost+=dist[v];
cnt++;
dist[v]=0;
for i=1 to N
if dist[i]不等于0且c[v][i]小于dist[i]
dist[i]等于c[v][i];
parent[i]等于v;
if(cnt等于N)输出cost;
else 输出-1;
}
伪代码为思路总结,不是简单翻译代码。