PTA 旅游规划(邻接矩阵) 思路分析及代码解析

一、前导

1. 需要掌握的知识

  1. 图的最短路径、Dijkstra最短路算法

2. 题目信息

  1. 题目来源:PTA / 拼题A
  2. 题目地址:旅游规划

二、解题思路分析

1. 题意理解

  1. 单源有权图最短路问题,在本题中有两个最短路维度:距离维度和价格维度

1. 1 输入数据

4 5 0 3 //城市数 4、高速公路数 5、起点以及终点 0 3
//如下5行是具体的高速公路信息:起点 终点 距离 价位
0 1 1 20 
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20

1.2 输出数据

  1. 输出起点到终点的最短路径及花费的金额。需要注意,当存在多条最短路径时,输出价位最低的那条路径
3 40 

2. 思路分析(重点)

  1. 这道题是一个单源无权图最短路径问题,与哈利·波特的考试属于同类题,同样使用Dijkstra算法解决
  2. 本题中最短路包含两个维度:两地之间的最短距离 和 两地之间的最低收费,其中最短距离优先级更高
  3. 从起点执行Dijkstra算法,从最短距离、最低收费两个维度进行代码的实现

三、具体实现

1. 弯路和bug

  1. 图的最短路问题,继续练习,以便熟能生巧

2. 代码框架(重点)

2.1 采用的数据结构

  1. 二维数组
#define max 500 //城市数最少2、最多500,城市编号从0开始
int Length[max][max], price[max][max]; //距离维度 和 价格维度 
  1. 对于本题,需要三个数组配合Dijkstra( )
int dist[max],cost[max]; //各顶点到源点的距离和费用 
bool check[max]; //用于标记顶点是否收录到虚拟集合S中 

2.2 程序主体框架

               程序伪码描述
int main()
{	
	1.通过二维数组存储图的顶点和边
	2.以起点为源点,执行Dijkstra( )
	return 0;
}

2.3 各分支函数

  1. Dijkstra( ) 本题AC的核心函数。对于Dijkstra算法,最短路径不是一次成型的,而是逐步成型的,他的具体步骤包括:
    (1) 令S为一个集合(对应check数组),集合S包括源点source 以及 已经确定了最短路径的顶点v。集合S一开始为空(对应check数组的元素值都是false),第一个收录进集合S的是源点
    (2) 对任意一个未收录的顶点v,定义 dist[v] 为:源点s到顶点v的最短路径,但该路径仅经过集合S中的顶点,即路径 { s–>(vi属于S)–>v }的最小长度
    (3) 除源点外,每次从未收录的顶点中选一个dist最小的收录,按递增(非递减)的顺序生成集合S
    (4) 增加一个顶点Node进入集合S时,可能影响Node的邻接点 i 的dist值(顶点i未在集合S中),当dist值变小时,需要执行更新操作
    问题:顶点i未在集合S中这个条件不在代码中体现,ms也能AC,为什么?另外,为什么仅更新未在集合S中的顶点呢,集合S中的顶点会不受影响吗?11.14 不体现意味着,对所有邻接点均进行检查,是一种算力资源的浪费;集合S中的顶点dist值不会受到影响,因为前提条件:所有顶点的最短路径需要经过集合S中的收录顶点
for(int i=0;i<CityNumber;i++)
{
	if(isEdge(Node,i))
		if(dist[i]>dist[Node]+Length[Node][i])
			dist[i]=dist[Node]+Length[Node][i];
}
  1. 依据上述步骤进行编码实现。本题还需要注意一点,当最短距离相等时,需要再次判定最低价格
void Dijkstra(vertex Source)
{
	vertex Node=-1;
	int dist_min=infinite;
	
	check[Source]=true;
	dist[Source]=0;
	cost[Source]=0;
	
	for(int i=0;i<CityNumber;i++)
	{
		if( isEdge(Source,i) )  //收入一个顶点Source,将Source邻接顶点的dist值更新
		{
			dist[i]=dist[Source]+Length[Source][i];
			cost[i]=cost[Source]+price[Source][i];
		}			
	}
	
	while(true)
	{
	
		for(int i=0;i<CityNumber;i++) //获取dist最小的顶点,然后收入集合S中 :按递增(非递减)的原则收入顶点 
		{
			if(check[i]) continue;
			if(dist[i]<dist_min)
			{
				dist_min=dist[i];
				Node=i;
			}		
		}
		
		if(Node==-1) break;  //Node就是未收录顶点中dist最小者,当Node==-1时,说明所有顶点已收入 
		
		check[Node]=true; //新顶点Node加入集合S后,需要更新和其有边的顶点 
		
		for(int i=0;i<CityNumber;i++)
		{
			if(isEdge(Node,i))
			{
				if(dist[i]>dist[Node]+Length[Node][i]) //dist[i] 表示顶点i距离源点的距离 
				{
					dist[i]=dist[Node]+Length[Node][i];
					cost[i]=cost[Node]+price[Node][i];
				}
				else if( dist[i]==dist[Node]+Length[Node][i] && cost[i]>cost[Node]+price[Node][i] )	//当最短路径(距离)相等时,需要再次判定最短路径(价格)
					cost[i]=cost[Node]+price[Node][i];
			}
		}
		
		dist_min=infinite; Node=-1; //恢复默认值
	}
}
  1. 其他函数与Harry Potter’s exam基本一致,在此不表

3. 完整编码

  1. 本文如果对你有帮助,请点赞鼓励 ,谢谢 😊
  2. 如有建议或意见,欢迎留言
#include <iostream>
using namespace std;

#define infinite 999
#define max 500 //城市数最少2、最多500,城市编号从0开始
typedef int vertex;

int Length[max][max], price[max][max]; //距离维度 和 价格维度 
int dist[max],cost[max]; //各顶点到源点的距离和费用 
bool check[max]; //用于标记顶点是否收录到虚拟集合S中 
int CityNumber,HighwayNumber,Source,Destination; 

void CreateGraphic();
void Dijkstra(vertex Source);
void initialize();
bool isEdge(vertex a,vertex b);
int main()
{
	CreateGraphic();
	Dijkstra(Source);
	cout<<dist[Destination]<<" "<<cost[Destination]<<endl;
	return 0;
} 

void Dijkstra(vertex Source)
{
	vertex Node=-1;
	int dist_min=infinite;
	
	check[Source]=true;
	dist[Source]=0;
	cost[Source]=0;
	
	for(int i=0;i<CityNumber;i++)
	{
		if( isEdge(Source,i) )  //收入一个顶点Source,将Source邻接顶点的dist值更新
		{
			dist[i]=dist[Source]+Length[Source][i];
			cost[i]=cost[Source]+price[Source][i];
		}			
	}
	
	while(true)
	{
	
		for(int i=0;i<CityNumber;i++) //获取dist最小的顶点,然后收入集合S中 :按递增(非递减)的原则收入顶点 
		{
			if(check[i]) continue;
			if(dist[i]<dist_min)
			{
				dist_min=dist[i];
				Node=i;
			}		
		}
		
		if(Node==-1) break;  //Node就是未收录顶点中dist最小者,当Node==-1时,说明所有顶点已收入 
		
		check[Node]=true; //新顶点Node加入集合S后,需要更新和其有边的顶点 
		
		for(int i=0;i<CityNumber;i++)
		{
			if(isEdge(Node,i))
			{
				if(dist[i]>dist[Node]+Length[Node][i]) //dist[i] 表示顶点i距离源点的距离 
				{
					dist[i]=dist[Node]+Length[Node][i];
					cost[i]=cost[Node]+price[Node][i];
				}
				else if( dist[i]==dist[Node]+Length[Node][i] && cost[i]>cost[Node]+price[Node][i] )	//当最短路径(距离)相等时,需要再次判定最短路径(价格)
					cost[i]=cost[Node]+price[Node][i];
			}
		}
		
		dist_min=infinite; Node=-1; //恢复默认值
	}
}


bool isEdge(vertex a,vertex b)
{
	if(Length[a][b])
		return true;
	else
		return false;
}

void initialize()
{
	for(int i=0;i<CityNumber;i++)
	{
		dist[i]=infinite;
		cost[i]=infinite;
		check[i]=false; 
	}
}

void CreateGraphic()
{
	int x,y,Distance,Money;
	
	cin>>CityNumber>>HighwayNumber>>Source>>Destination;
	
	for(int i=0;i<CityNumber;i++)
	{
		for(int j=0;j<CityNumber;j++)
		{
			Length[i][j]=0;
			Length[j][i]=0;
			price[i][j]=0;
			price[j][i]=0;
		}
	}
	
	for(int k=0;k<HighwayNumber;k++)
	{
		cin>>x>>y>>Distance>>Money;
		Length[x][y]=Distance;
		Length[y][x]=Distance;
		price[x][y]=Money;
		price[y][x]=Money;
	}
	
	initialize();
	
	return;
}

四、参考

  1. 浙江大学 陈越、何钦铭老师主讲的数据结构
  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值