PTA Saving James Bond - Hard Version 思路分析及代码解析

PTA Saving James Bond - Hard Version 思路分析及代码解析v0.9.1

  • 一、前导
    • 1. 需要掌握的知识
    • 2. 题目信息
  • 二、解题思路分析
    • 1. 题意理解
      • 1. 1 输入数据
      • 1.2 输出数据
    • 2. 思路分析(重点)
  • 三、具体实现
    • 1. 弯路和bug
    • 2. 代码框架(重点)
      • 2.1 采用的数据结构
      • 2.2 程序主体框架
      • 2.3 各分支函数
    • 3. 完整编码
  • 四、参考

一、前导

1. 需要掌握的知识

  1. 图的最短路径、BFS

2. 题目信息

  1. 题目来源:PTA / 拼题A
  2. 题目地址:Saving James Bond - Hard Version

二、解题思路分析

1. 题意理解

  1. Saving James Bond - Easy Version 的加强版,不仅要告诉Bond是否可以获救,还要告诉他具体的逃脱路径

1. 1 输入数据

17 15  //岛中共有17条鳄鱼:下面17行数据,表示每条鳄鱼的横坐标和纵坐标;Bond的最大跨越距离是15
10 -21
10 21
-40 10
30 -50
20 40
35 10
0 -10
-25 22
40 -40
-30 30
-10 22
0 11
25 21
25 10
10 10
10 35
-30 10

1.2 输出数据

  1. 输出最短路径长度及相关鳄鱼的坐标(从岛向岸的顺序输出)
4
0 11
10 21
10 35 

2. 思路分析(重点)

  1. 这道题本质是一个单源无权图最短路径问题,表面看起来,在 Saving James Bond - Easy Version的基础上,通过优化BFS算法就可以解决掉
  2. 但实际上还是蛮有难度的,依据题目的输出要求,我们可以看到 If there are many shortest paths, just output the one with the minimum first jump, which is guaranteed to be unique.(当有多条最短路时,输出第一跳最小的最短路) 首先需要对图中的相关顶点(鳄鱼坐标),按第一跳的距离从小到大排序
  3. 从岛中心开始进行BFS,按第一跳从小到大的顺序收入顶点,在遍历顶点的过程中,实时记录各顶点到源点的距离(dist数组) 以及 当前顶点的前一跳顶点(path数组),这样才能满足题目的输出要求

三、具体实现

1. 弯路和bug

  1. 注意输出要求:我一开始忽视了当有多条最短路时,输出第一跳最小的最短路的输出要求,被卡了很久

2. 代码框架(重点)

2.1 采用的数据结构

  1. 通过结构体数组存储鳄鱼的坐标
#define MaxNodes 101 
//不考虑岸的情况下,图中最多有101个顶点:岛和100只鳄鱼
struct node
{
     
	int x;
	int y;
}a[MaxNodes];
  1. 对于本题,需要三个数组配合DFS( )
int FirstDistance[MaxNodes]; //将鳄鱼顶点按第一跳的距离从小到大排序,存储到FirstDistance数组
int dist[MaxNodes];//顶点i到源点的距离
int path[MaxNodes];//记录跳跃路径 

2.2 程序主体框架

               程序伪码描述
int main()
{
     	
	1.通过结构体数组记录鳄鱼坐标
	2.将鳄鱼顶点按第一跳的距离从小到大排序
	3.优化后的DFS算法
	 3.1 从源点开始,按第一跳从近到远的距离遍历图中的顶点
	 3.2 若满足登岸要求,打印数据;不满足时进行循环
	return 0;
}

2.3 各分支函数

  1. GetNodesFirstDistance( ) 重点函数,将鳄鱼顶点按第一跳的距离从小到大排序,是后面DFS( )函数的基础。程序实现难度不高,但是有点绕
void GetNodesFirstDistance()
{
     
	sort(FirstDistance+1,FirstDistance+NodesNumber+1,CompareFirstJump); 
	//通过sort()函数完成排序,需要#include 
} 

void initialize()
{
     
	for(int i=0;i<=NodesNumber;i++)
	{
     
		FirstDistance[i]=i; // i=1 代表顶点1,也就是录入数据中第一只鳄鱼的坐标 
	}
}

bool CompareFirstJump(vertex a,vertex b)
{
     
	return  firstjump(a) < firstjump(b); 
} //按第一跳从小到大的顺序排序

int firstjump(vertex n)
{
     
	int dist, james_jump;
	dist=(a[n].x*a[n].x) + (a[n].y*a[n].y);
	james_jump=(radius+BondJumpDist)*(radius+BondJumpDist); //第一跳有岛的加成 
	if(james_jump>=dist) 
		return dist;
	else 
		return Null;
}
  1. BFS( ) 与普通的BFS不同,需要通过dist数组记录顶点到源点的距离、通过path数据记录最短路径中的图顶点。由于是无权图,dist每次+1即可。
    另外,将第一跳可达的顶点,需要按从近到远的距离压入队列
typedef int vertex; 
void BFS()
{
     
	vertex Node,currentNode;
	queue<vertex> q;
	q.push(source);
	currentNode=q.front();
	q.pop();
	dist[currentNode]=0;
	path[currentNode]=Null;
	 
	for(int i=1;i<=NodesNumber;i++) //将第一跳可达的顶点,按从近到远的距离压入队列 
	{
     
		Node=FirstDistance[i];
		if(firstjump(Node)==Null) continue; //第一跳为Null,说明不可达
		else
		{
     
			q.push(Node);
			dist[Node]=dist[currentNode]+1;
			path[Node]=currentNode;
		}	 
	}
	
	while(!q.empty()) 
	{
     
		currentNode=q.front();
		q.pop();
		
		if(isSave(currentNode)) 
		{
     
			PrintResult(currentNode);//满足登岸要求后执行打印
			return ;
		}
		for(int i=1;i<=NodesNumber;i++)//不满足继续循环
		{
     
			if(jump(i,currentNode) && dist[i]==Null)
			{
     
				q.push(i);
				dist[i]=dist[currentNode]+1;
				path[i]=currentNode;
			}
		}	
	}
	
	cout<<"0"<<endl;
}
  1. PrintResult( ) 满足要求后打印结果,因为要按照从岛到岸的方向打印,所以使用堆栈进行输出:依据path数据,先入栈、再依次出栈
void PrintResult(vertex currentNode)
{
     
	int top;
	stack<vertex> s;
	cout<<dist[currentNode]+1<<endl; //从鳄鱼到岸还要跳一次,所以+1
	
	while(true)
	{
     
		s.push(currentNode);
		currentNode=path[currentNode];
		if(currentNode==source) break;
	}
	
	while(!s.empty())
	{
     
		top=s.top();
		s.pop();
		cout<<a[top].x<<" "<<a[top].y<<endl;
	}
	return;
}
  1. isSave( ), jump( ) 与easy version一致,在此不表

3. 完整编码

#include  //sort 
#include 
#include  
#include 
using namespace std;

typedef int vertex; 

#define MaxNodes 101 // 不考虑岸的情况下,图中有101个顶点:岛和100只鳄鱼 (岸是隐藏顶点) 
#define distance 42.5 //岛的边缘到岸上的距离 
#define radius 7.5  //岛半径 
#define Null -1
#define source 0 
 
struct node
{
     
	int x;
	int y;
}a[MaxNodes];

int FirstDistance[MaxNodes];  // If there are many shortest paths, just output the one with the minimum first jump . 据此,需要将100只鳄鱼 到 岛的距离,按从小到大排序。若第一跳无法到达,输出Null
int dist[MaxNodes];
int path[MaxNodes]; 

int NodesNumber,BondJumpDist; 

bool jump(int New,int Old);
bool isSave(int n);

int BFS(int n); //Breadth First Search

void GetNodesFirstDistance(); //获取各顶点的第一跳距离,并按从小到大排序,Null代表第一跳不可达 
void initialize(); //初始化dist path FirstDistance数组 
bool CompareFirstJump(vertex a,vertex b);
int firstjump(vertex n);
void PrintResult(vertex currentNode);
void BFS();

int main()
{
     
	int x,y;
	
	cin>>NodesNumber>>BondJumpDist; 
	
	for(int i=1;i<=NodesNumber;i++) //无论是否愿意,所有的输入数据必须先接收 . i=0代表岛,也就是源点 
	{
     
		cin>>x>>y;	
		a[i].x=x; 
		a[i].y=y;
	}
	
	if(BondJumpDist>=distance) //Bond天纵奇才,从小岛到岸边,一跳结束战斗 
	{
     
		cout<<'1';
		return 0;
	}
	
	initialize(); //初始化dist path  FirstDistance 三个数组 
	
	GetNodesFirstDistance();//获取各顶点的第一跳距离,使得FirstDistance数组 从小到大排序,Null代表第一跳不可达
	
	BFS();
	
	return 0;
}

void BFS()
{
     
	vertex Node,currentNode;
	queue<vertex> q;
	q.push(source);
	currentNode=q.front();
	q.pop();
	dist[currentNode]=0;
	path[currentNode]=Null;
	 
	for(int i=1;i<=NodesNumber;i++) //将第一跳可达的顶点,按从近到远的距离压入队列 
	{
     
		Node=FirstDistance[i];
		if(firstjump(Node)==Null) continue; //第一跳为Null,说明不可达
		else
		{
     
			q.push(Node);
			dist[Node]=dist[currentNode]+1;
			path[Node]=currentNode;
		}	 
	}
	
	while(!q.empty()) 
	{
     
		currentNode=q.front();
		q.pop();
		
		if(isSave(currentNode)) 
		{
     
			PrintResult(currentNode);//满足登岸要求后执行打印
			return ;
		}
		for(int i=1;i<=NodesNumber;i++)//不满足继续循环
		{
     
			if(jump(i,currentNode) && dist[i]==Null)
			{
     
				q.push(i);
				dist[i]=dist[currentNode]+1;
				path[i]=currentNode;
			}
		}	
	}
	
	cout<<"0"<<endl;
}

void PrintResult(vertex currentNode)
{
     
	int top;
	stack<vertex> s;
	cout<<dist[currentNode]+1<<endl; //从鳄鱼到岸还要跳一次,所以+1
	
	while(true)
	{
     
		s.push(currentNode);
		currentNode=path[currentNode];
		if(currentNode==source) break;
	}
	
	while(!s.empty())
	{
     
		top=s.top();
		s.pop();
		cout<<a[top].x<<" "<<a[top].y<<endl;
	}
	return;
}

bool jump(int New,int Old)
{
     
	int dist, james_jump;
	dist=(a[New].x-a[Old].x)*(a[New].x-a[Old].x) + \
	 (a[New].y-a[Old].y)*(a[New].y-a[Old].y);
	james_jump=BondJumpDist*BondJumpDist;
	if(james_jump>=dist) return true;
	else return false;
}

bool isSave(int n)
{
     
	if(a[n].x+BondJumpDist >= 50 || a[n].x-BondJumpDist <= -50 || \
	a[n].y+BondJumpDist >=50 || a[n].y-BondJumpDist <= -50 ) 
		return true;
	else return false;
}


void initialize()
{
     
	for(int i=0;i<=NodesNumber;i++)
	{
     
		dist[i]=Null;
		path[i]=Null;
		FirstDistance[i]=i; // i=1 代表顶点1,也就是录入数据中第一只鳄鱼的坐标 
	}
}

void GetNodesFirstDistance()
{
     
	sort(FirstDistance+1,FirstDistance+NodesNumber+1,CompareFirstJump); //sort
} 

bool CompareFirstJump(vertex a,vertex b)
{
     
	return  firstjump(a) < firstjump(b); 
} 

int firstjump(vertex n)
{
     
	int dist, james_jump;
	dist=(a[n].x*a[n].x) + (a[n].y*a[n].y);
	james_jump=(radius+BondJumpDist)*(radius+BondJumpDist); //第一跳有岛的加成 
	if(james_jump>=dist) 
		return dist;
	else 
		return Null;
}

四、参考

  1. 浙江大学 陈越、何钦铭老师主讲的数据结构

你可能感兴趣的:(数据结构学习,数据结构,算法,图论)