1. 程式人生 > 其它 >PTA Saving James Bond - Hard Version 思路分析及程式碼解析

PTA Saving James Bond - Hard Version 思路分析及程式碼解析

技術標籤:資料結構學習資料結構演算法圖論

PTA Saving James Bond - Hard Version 思路分析及程式碼解析v0.9.1

一、前導

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<algorithm> 
} 

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<algorithm>  //sort 
#include <stack>
#include <queue> 
#include <iostream>
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. 浙江大學 陳越、何欽銘老師主講的資料結構