PTA 哈利·波特的考试 思路分析及代码解析v0.9.1
一、前导
1. 需要掌握的知识
- 图的最短路径、Dijkstra最短路算法/迪科斯彻最短路算法
2. 题目信息
- 题目来源:PTA / 拼题A
- 题目地址:哈利·波特的考试
二、解题思路分析
1. 题意理解
-
不要被魔法的外衣所欺骗 😊,他本质就是一个无向有权图最短路问题:
(1)各种动物可以抽象为图的顶点
(2)不同的动物之间若可以变形 说明 图的顶点之间存在边、魔咒的长度就是边的权值
(3)魔咒倒过来也能用 代表 无向图
(4)根据猫、鼠、鱼的例子,设猫、鼠、鱼的顶点编号分别为 1 2 3,据下图可知 各顶点的最短路分别为(0,4,6) (4,0,4) (6,4,0) ,随后比较各顶点最短路的最大值,其中的最小值就是答案(有点绕,需要想一想)
-
解决无向有权图图最短路问题,可以使用Dijkstra最短路算法,只要正确实现 Dijkstra( ) ,就可以AC本题
1. 1 输入数据
6 11 //图的顶点数为6,边数为11;如下11行为边的详情
3 4 70 //顶点3和顶点4存在边,边的权重为70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80
1.2 输出数据
- 找到各个顶点作为源点时的最短路,随后比较各顶点最短路的最大值,输出其中最小值的顶点编号 及 最小值
- 若图不连通,输出0;若存在多个相同的最小值,输出顶点编号最小的
4 70
2. 思路分析(重点)
- 使用二维数组存储有权无向图
- 通过Dijkstra算法求得图中各个源点的最短路
- 进行数据比较,获取题目要求的数据并打印
三、具体实现
1. 弯路和bug
- 熟能生巧,最短路算法需要多编码练习
2. 代码框架(重点)
2.1 采用的数据结构
- 通过二维数组存储数据
#define max 101 //依据题意,最多100个顶点
int a[max][max];
- 对于本题,需要三个数组配合实现Dijkstra( )
int dist[v]; //顶点v到源点s的路径长度
int result[]; //存储源点s的最短路径中的最大值
bool check[]; //表示顶点是否已存储到最短路径顶点集合中
2.2 程序主体框架
程序伪码描述
int main()
{
1.创建图
2.通过循环语句,对各顶点依次使用Dijkstra算法
3.进行数据比较
return 0;
}
2.3 各分支函数
- Dijkstra( ) 核心函数,在正式编码之前,需要简单说明下Dijkstra算法,对于Dijkstra算法来说,最短路径不是一次成型的,而是逐步成型的,他的具体步骤包括:
(1) 令S为一个集合,该集合包括源点source 以及 已经确定了最短路径的顶点v
(2) 对任意一个未收录的顶点v,定义 dist[v] 为:源点s到顶点v的最短路径,但该路径仅经过集合S中的顶点,即路径 { s–>(vi属于S)–>v }的最小长度
(3) 每次从未收录的顶点中选一个dist最小的收录(按递增的顺序生成集合S):贪心算法
(4) 增加一个顶点v进入集合S时,可能影响另一个顶点w的dist值,所以需要进行判定。
举个栗子:以下图为例,先收录源点1进入集合S[1],此时的最短路为(0,4,6),按照算法,再收录源点2进入集合S[1,2],此时的最短路为(0,4,5) :由于2的加入,顶点3到源点1的最短路径发生了变化
依据上述步骤进行具体实现
int Dijkstra(int Source)
{
int dist_mini=infinite, dist_max=0 , vertex=-1; //每次从未收录的顶点中选一个dist最小的收录,变量vertex表示dist最小的顶点
check[Source]=true;//将源点收录到集合S中,check[]对应集合S,数组元素默认值为false
dist[Source] = 0; //源点到源点的长度设为0,以便区分
for(int i=1;i<=Nodes;i++) //更新dist[v] , 顶点v到Source的最短路径, dist[]默认值均为 无穷大
{
if( isEdge(Source,i) ) //第一次收录的是源点
dist[i]=a[Source][i];
}
while(true) //while循环开始收录非源点的顶点到集合S,直到所有连通的顶点都收录进去
{
for(int i=1;i<=Nodes;i++) //找到 未收录的顶点中 dist最小的顶点
{
if(dist[i] < dist_mini && !check[i] )
{
dist_mini=dist[i];
vertex=i;
}
}
if(vertex==-1) break; //所有满足条件的顶点都已经收录,退出循环
check[vertex]=true; //新的顶点已经收录,然后更新与其有边的顶点dist
for(int i=1;i<=Nodes;i++)
{
if( isEdge(vertex,i) && dist[vertex]+a[vertex][i] < dist[i] ) //core sentence!
dist[i]=dist[vertex]+a[vertex][i];
}
dist_mini=infinite, vertex=-1; //初始化
}
for(int i=1;i<=Nodes;i++)
if(dist[i]>dist_max)
dist_max=dist[i];
return dist_max;
}
- CreateGraphic( ) 创建图,属于基本功
- Default( ) 每个顶点执行一次 Dijkstra( ) 后,需要初始化dist数组和check数组
void CreateGraphic()
{
int first,last,value;
cin>>Nodes>>Edge;
for(int i=1;i<=Nodes;i++)
{
for(int j=1;j<=Nodes;j++)
a[i][j]=Null;
}
for(int k=1;k<=Edge;k++)
{
cin>>first>>last>>value;
a[first][last]=value;
a[last][first]=value;
}
Default();
return;
}
void Default()
{
for(int i=1;i<=Nodes;i++)
{
dist[i]=infinite; //顶点i到源点的距离 ,不能设定为0,若为0,则无法判定出源点(设源点到源点为0)
check[i]=false; //默认都不在集合S中
}
return;
}
3. 完整编码
#include<iostream>
using namespace std;
#define max 101
#define Null 0
#define infinite 99999999
int a[max][max];
int dist[max];
int result[max];
bool check[max];
int Nodes,Edge;
void CreateGraphic();
void Default(); //初始化
int Dijkstra(int Source);
bool isEdge(int i, int j);
int main()
{
int vertex, result_min=infinite;//result_min 比较各顶点最短路的最大值,找到其中最小值
CreateGraphic();
for(int i=1;i<=Nodes;i++)
{
result[i]=Dijkstra(i);
Default();
}
for(int i=1;i<=Nodes;i++)
{
if(result[i]==infinite) //结果为无穷大,说明不是连通图
{
cout<<"0";
return 0;
}
if(result[i]<result_min)
{
result_min=result[i];
vertex=i;
}
}
cout<<vertex<<" "<<result_min;
return 0;
}
bool isEdge(int i, int j)
{
if(!a[i][j])
return false;
else
return true;
}
int Dijkstra(int Source)
{
int dist_mini=infinite, dist_max=0 , vertex=-1; //每次从未收录的顶点中选一个dist最小的收录,变量vertex表示dist最小的顶点
check[Source]=true;//将源点收录到集合S中,check[]对应集合S,数组元素默认值为false
dist[Source] = 0; //源点到源点的长度设为0,以便区分
for(int i=1;i<=Nodes;i++) //更新dist[v] , 顶点v到Source的最短路径, dist[]默认值均为 无穷大
{
if( isEdge(Source,i) ) //第一次收录的是源点
dist[i]=a[Source][i];
}
while(true) //while循环开始收录非源点的顶点到集合S,直到所有连通的顶点都收录进去
{
for(int i=1;i<=Nodes;i++) //找到 未收录的顶点中 dist最小的顶点
{
if(dist[i] < dist_mini && !check[i] )
{
dist_mini=dist[i];
vertex=i;
}
}
if(vertex==-1) break; //所有满足条件的顶点都已经收录,退出循环
check[vertex]=true; //新的顶点已经收录,然后更新与其有边的顶点dist
for(int i=1;i<=Nodes;i++)
{
if( isEdge(vertex,i) && dist[vertex]+a[vertex][i] < dist[i] ) //core sentence!
dist[i]=dist[vertex]+a[vertex][i];
}
dist_mini=infinite, vertex=-1; //初始化
}
for(int i=1;i<=Nodes;i++)
if(dist[i]>dist_max)
dist_max=dist[i];
return dist_max;
}
void CreateGraphic()
{
int first,last,value;
cin>>Nodes>>Edge;
for(int i=1;i<=Nodes;i++)
{
for(int j=1;j<=Nodes;j++)
a[i][j]=Null;
}
for(int k=1;k<=Edge;k++)
{
cin>>first>>last>>value;
a[first][last]=value;
a[last][first]=value;
}
Default();
return;
}
void Default()
{
for(int i=1;i<=Nodes;i++)
{
dist[i]=infinite; //顶点i到源点的距离 ,不能设定为0,若为0,则无法判定出源点(设源点到源点为0)
check[i]=false; //默认都不在集合S中
}
return;
}