题目:
This time let us consider the situation in the movie “Live and Let Die” in which James Bond, the world’s most famous spy, was captured by a group of drug dealers. He was sent to a small piece of land at the center of a lake filled with crocodiles. There he performed the most daring action to escape – he jumped onto the head of the nearest crocodile! Before the animal realized what was happening, James jumped again onto the next big head… Finally he reached the bank before the last crocodile could bite him (actually the stunt man was caught by the big mouth and barely escaped with his extra thick boot).
Assume that the lake is a 100 by 100 square one. Assume that the center of the lake is at (0,0) and the northeast corner at (50,50). The central island is a disk centered at (0,0) with the diameter of 15. A number of crocodiles are in the lake at various positions. Given the coordinates of each crocodile and the distance that James could jump, you must tell him a shortest path to reach one of the banks. The length of a path is the number of jumps that James has to make.
Input Specification:
Each input file contains one test case. Each case starts with a line containing two positive integers N (≤100), the number of crocodiles, and D, the maximum distance that James could jump. Then N lines follow, each containing the (x,y) location of a crocodile. Note that no two crocodiles are staying at the same position.
Output Specification:
For each test case, if James can escape, output in one line the minimum number of jumps he must make. Then starting from the next line, output the position (x,y) of each crocodile on the path, each pair in one line, from the island to the bank. If it is impossible for James to escape that way, simply give him 0 as the number of jumps. If there are many shortest paths, just output the one with the minimum first jump, which is guaranteed to be unique.
Sample Input 1:
17 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
Sample Output 1:
4
0 11
10 21
10 35
Sample Input 2:
4 13
-12 12
12 12
-12 -12
12 -12
Sample Output 2:
0
手绘示意图:
基本逻辑和easy版本的一样。
算法逻辑与分析:
1.根据输入构造图
1.1顶点和边的表达
1.1.1顶点包括鳄鱼,湖心岛中心和湖岸;注:需要去除在岛上,岸边的鳄鱼。
为何要对顶点和邻接表排序?
根据题目输出要求:优选有最小第一条的路径。
If there are many shortest paths, just output the one with the minimum first jump,
which is guaranteed to be unique.
对顶点按从近到远排序和对邻接表按从近到远排序,保证优先处理“最小第一跳”。
1.1.2对于顶点和邻接表用typedef结构数组的方式表示,而不用结构数组指针,更方便调试。
1.1.3边是动态生成的,所以没有必要用Edge结构表达和传递
1.1.4边是跳跃距离D,根据跳跃距离,连接顶点,构造图
2.对图求最短路径
典型的无权单源最短路径算法,一点改动是需要对当前的顶点做判断,是否已经是出口
3.输出最小路径
自定义堆栈输出
代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#define MaxVertexNum 101 // 最多100条鳄鱼加上湖心岛
#define DistToBank 50
#define IslandRadius 7.5
#define NoExit -1
typedef int Vertex;
// 队列
typedef struct _Queue *Queue;
struct _Queue
{
Vertex *Data;
int front;
int rear;
};
// 鳄鱼坐标(Coordinate Of Crocodile)
typedef struct _Coordinate
{
int x, y;
} Coordinate[MaxVertexNum];
// 邻接点
typedef struct _AdjNode *AdjNode;
struct _AdjNode
{
Vertex AdjV;
AdjNode Next;
};
// 邻接表
typedef struct _AdjTable
{
AdjNode FirstEdge;
} AdjTable[MaxVertexNum];
// 领接表表示的图
typedef struct _LGraph *LGraph;
struct _LGraph
{
int Nv, Ne, Weight; // 因为每跳是固定的值,所以Weight放在这里处理更简单
Coordinate Crocodiles; // 鳄鱼坐标结构数组
AdjTable Graph; // 图
};
Queue createQ(int Nv);
void addQ(Queue Q, Vertex V);
Vertex delQ(Queue Q);
bool isEmpty(Queue Q);
LGraph createGraph(Coordinate Crocodiles, int Nv, int Weight);
bool isOnIsland(int x, int y);
bool isOnBank(int x, int y);
void sortVertexFromNearToFar(Coordinate Crocodiles, int Nv);
void sortAdjTableFromSmallToLarge(AdjNode FirstEdge);
bool isEdge(LGraph G, Vertex V, Vertex W);
void insertEdge(LGraph G, Vertex V, Vertex W);
bool isExit(LGraph G, Vertex V);
LGraph buildGraph();
Vertex BFS(LGraph G, Vertex S, int dist[], int path[]);
void save007(LGraph G);
void showPath(LGraph G, Vertex ExitID, int dist[], int path[]);
void freeGraph(LGraph G);
/*
输入1:
17 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:
4
0 11
10 21
10 35
输入2:
4 13
-12 12
12 12
-12 -12
12 -12
输出2:
0
*/
int main()
{
LGraph G = buildGraph();
save007(G);
freeGraph(G);
return 0;
}
// 队列相关操作集
Queue createQ(int Nv)
{
Queue Q = (Queue)malloc(sizeof(struct _Queue));
Q->Data = malloc(Nv * sizeof(int));
Q->front = Q->rear = -1;
return Q;
}
void addQ(Queue Q, Vertex V)
{
Q->rear++;
Q->Data[Q->rear] = V;
}
Vertex delQ(Queue Q)
{
Q->front++;
return Q->Data[Q->front];
}
bool isEmpty(Queue Q)
{
return Q->front == Q->rear ? true : false;
}
// 建图
LGraph buildGraph()
{ // N鳄鱼数量(顶点数),D跳跃距离(边的权重),(x,y)鳄鱼坐标,CrocodilesInLake有效的鳄鱼数
int N, D, x, y, CrocodilesInLake = 0;
scanf("%d %d", &N, &D);
// 读入鳄鱼坐标
Coordinate Crocodiles;
for (int i = 0; i < N; i++)
{
scanf("%d %d", &x, &y);
// 剔除不在湖里的鳄鱼
if (!isOnIsland(x, y) && !isOnBank(x, y))
{
Crocodiles[CrocodilesInLake].x = x;
Crocodiles[CrocodilesInLake].y = y;
CrocodilesInLake++;
}
}
// 将顶点从近到远排序
sortVertexFromNearToFar(Crocodiles, CrocodilesInLake);
// 建立只含顶点的初始图
LGraph G = createGraph(Crocodiles, CrocodilesInLake, D);
// 插入边
Vertex V, W;
for (V = 0; V < G->Nv - 1; V++)
{
for (W = V + 1; W < G->Nv; W++)
{
if (isEdge(G, V, W))
{ // 因为V,W不是固定的,而Weight又是固定的,所以没有必要先构造Edge,再插入
insertEdge(G, V, W);
}
}
}
// 邻接表排序
for (V = 0; V < G->Nv; V++)
{
sortAdjTableFromSmallToLarge(G->Graph[V].FirstEdge);
}
// 完成图的创建
return G;
}
// 在岛上的鳄鱼
bool isOnIsland(int x, int y)
{
float dist = sqrt(pow(x, 2) + pow(y, 2));
if (dist <= IslandRadius)
{
return true;
}
else
{
return false;
}
}
// 在岸上的鳄鱼
bool isOnBank(int x, int y)
{
if (abs(x) >= DistToBank || abs(y) >= DistToBank)
{
return true;
}
else
{
return false;
}
}
// 将顶点从近到远排序[选择排序]
void sortVertexFromNearToFar(Coordinate Crocodiles, int Nv)
{
int i, j, min;
// Coordinate Temp;
struct _Coordinate Temp;
for (i = 0; i < Nv - 1; i++)
{
min = i;
for (j = i + 1; j < Nv; j++)
{
if (pow(Crocodiles[min].x, 2) + pow(Crocodiles[min].y, 2) >
pow(Crocodiles[j].x, 2) + pow(Crocodiles[j].y, 2))
{
min = j;
}
}
if (min != i)
{
Temp = Crocodiles[i];
Crocodiles[i] = Crocodiles[min];
Crocodiles[min] = Temp;
}
}
}
// 建立只含顶点的初始图
LGraph createGraph(Coordinate Crocodiles, int Nv, int Weight)
{
LGraph G = (LGraph)malloc(sizeof(struct _LGraph));
G->Nv = Nv + 1; // 将小岛也作为一个顶点归入图中,所以+1;
G->Ne = 0;
G->Weight = Weight;
G->Crocodiles[0].x = G->Crocodiles[0].y = 0; // 小岛坐标
Vertex V;
for (V = 1; V < G->Nv; V++)
{
G->Crocodiles[V] = Crocodiles[V - 1]; // 读入顶点
}
for (V = 0; V < G->Nv; V++)
{
G->Graph[V].FirstEdge = NULL;
}
return G;
}
// 判断两个顶点是否有边
bool isEdge(LGraph G, Vertex V, Vertex W)
{
bool ret = false;
float dist = sqrt(pow(G->Crocodiles[W].x - G->Crocodiles[V].x, 2) +
pow(G->Crocodiles[W].y - G->Crocodiles[V].y, 2));
if (V == 0)
{ // 小岛要单独考虑
if (dist - IslandRadius <= G->Weight)
{
ret = true;
}
}
else
{
if (dist <= G->Weight)
{
ret = true;
}
}
return ret;
}
// 将边插入图中
void insertEdge(LGraph G, Vertex V, Vertex W)
{
AdjNode newNode;
newNode = (AdjNode)malloc(sizeof(struct _AdjNode));
newNode->AdjV = W;
newNode->Next = G->Graph[V].FirstEdge;
G->Graph[V].FirstEdge = newNode;
// 用邻接表表示无权图,1条边要插入2次
newNode = (AdjNode)malloc(sizeof(struct _AdjNode));
newNode->AdjV = V;
newNode->Next = G->Graph[W].FirstEdge;
G->Graph[W].FirstEdge = newNode;
G->Ne++;
}
void sortAdjTableFromSmallToLarge(AdjNode FirstEdge)
{
Vertex Temp;
AdjNode p, q;
for (p = FirstEdge; p; p = p->Next)
{
for (q = p->Next; q; q = q->Next)
{
if (q->AdjV < p->AdjV)
{
Temp = q->AdjV;
q->AdjV = p->AdjV;
p->AdjV = Temp;
}
}
}
}
// 判断是否是出口
bool isExit(LGraph G, Vertex V)
{
bool ret = false;
if (V == 0)
{ // 能否从岛上跳到岸边?
if ((DistToBank - IslandRadius) <= G->Weight)
{
ret = true;
}
}
else if (DistToBank - abs(G->Crocodiles[V].x) <= G->Weight ||
DistToBank - abs(G->Crocodiles[V].y) <= G->Weight)
{
ret = true;
}
return ret;
}
Vertex BFS(LGraph G, Vertex S, int dist[], int path[])
{
Vertex V, ExitID = NoExit;
AdjNode W;
dist[S] = 0;
Queue Q = createQ(G->Nv);
addQ(Q, S);
while (!isEmpty(Q))
{
V = delQ(Q);
// 由于是递增的遍历,所以第一个找到的出口一定是首跳最短路径出口,可以break出来
if (isExit(G, V))
{
ExitID = V;
break;
}
for (W = G->Graph[V].FirstEdge; W; W = W->Next)
{
if (dist[W->AdjV] == -1)
{
dist[W->AdjV] = dist[V] + 1;
path[W->AdjV] = V;
addQ(Q, W->AdjV);
}
}
}
free(Q->Data);
free(Q);
return ExitID;
}
// 拯救007
void save007(LGraph G)
{
int dist[G->Nv], path[G->Nv];
Vertex V;
for (V = 0; V < G->Nv; V++)
{
dist[V] = path[V] = -1;
}
// BFS遍历寻找出口,并带出出口值
Vertex ExitID = BFS(G, 0, dist, path);
// 按题目要求输出
if (ExitID == NoExit)
{
printf("0\n");
}
else
{
showPath(G, ExitID, dist, path);
}
}
// 利用堆栈输出具体路径
void showPath(LGraph G, Vertex ExitID, int dist[], int path[])
{
int minJumps = dist[ExitID] + 1; // 要加上最后从出口鳄鱼跳上岸的那一步
printf("%d\n", minJumps);
Vertex Stack[G->Nv];
int Top = -1;
while (ExitID != -1)
{
Top++;
Stack[Top] = ExitID;
ExitID = path[ExitID];
}
Top--; // 最上面存的是小岛的坐标,不需要输出
while (Top != -1)
{
printf("%d %d\n", G->Crocodiles[Stack[Top]].x, G->Crocodiles[Stack[Top]].y);
Top--;
}
}
void freeGraph(LGraph G)
{
AdjNode p, q;
Vertex V;
for (V = 0; V < G->Nv; V++)
{
for (p = G->Graph[V].FirstEdge; p; p = q)
{
q = p->Next;
free(p);
}
}
// G->Crocodiles和G->Graph不是通过malloc分配的空间,所以无需用free释放
// free(G->Crocodiles);
// free(G->Graph);
free(G);
}
测试结果:
小结:
非常精彩的一道题,是无权图单源最短路的一个典型应用,需要对该算法有有深刻的认识,并灵活应用。