中国大学MOOC-数据结构基础习题集、06-1、Saving James Bond - Hard Version
题目链接:http://www.patest.cn/contests/mooc-ds/06-1
题目分析:这是一道考察图的广度优先遍历,同时也要借助于Dijstra算法的一道题。题目的背景与上周的05-2是相同的:007被困在一个孤岛上,孤岛的直径是15,池塘的范围是[±50, ±50]。池塘中鳄鱼的条数及坐标,007的跳跃半径通过输入给出。问007能否借助于池塘中的鳄鱼逃出生天?和上周要求不同的是,需要把最短路径长度及最短路径输出,在有多条最短路径的情况下,输出第一跳最近的那条。
特别说明:
1. 是否建立图随意,用若干个数组表示也可以。博主最开始是没有建的,但是这次贴的代码是建好图的(邻接表)。一方面是为了练习自己的编码能力,一方面也是给对建图不熟悉的同学们举个“栗子”。感兴趣的同学可以关注下103~158行。
2. 题目中要求“多条最短路径输出第一跳最近的那条”,博主查找了许多别的资料,最后使用的优先级队列。使用时需要重载小于运算符(即operator<)。将在孤岛上能踩到的所有鳄鱼结点,以距孤岛中心的距离为优先级,放入优先级队列中。然后依次取出,做广度优先遍历。这部分在165~180+行。
3. 在做广度优先遍历时,如果已经可以逃脱,要确定当前是否为最短路径,是则记录路径。可以关注一下198~215行。
4. 求两点间距离的函数,要判断其中一点是不是“岛屿”,如果是的话,结果要加上孤岛的半径。函数的定义在63~78行。
5. 如果Case4过不去的朋友,请试一下我的用例1。如果是Case5过不去的朋友,请试一我的用例2。
建议测试如下数据:
4 20 -27 0 -41 0 0 26 0 40
博主这里输出结果是:3 0 26 0 40
1 42.5
博主这里输出结果是:1
代码分析:
注释写的非常详细,大家可以阅读一下:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <cmath> 5 #include <queue> 6 #include <stack> 7 8 using namespace std; 9 10 /* 11 * 所用结构体的声明 12 * pos 坐标结构体 13 * vexNode 邻接表中的结点 14 */ 15 struct pos; 16 template <class T> struct vexNode; 17 18 /* 19 * 宏定义的声明 20 * FIRSTSTEP 小岛的半径,固定为7.5 21 * BORDER 边界的大小,固定为50 22 * MAXNUM 无穷大 23 */ 24 25 #define FIRSTSTEP 7.5 26 #define BORDER 50 27 #define MAXNUM 100000000 28 29 /* 30 * 全局变量的声明 31 * vec 存储鳄鱼的坐标 32 * eVec 邻接表存储图 33 * pathVec 用来存储路径 34 */ 35 36 vector<pos> vec; 37 vector<vexNode<int> > eVec; 38 vector<int> pathVec; 39 40 struct pos 41 { 42 double x; 43 double y; 44 pos(double a, double b):x(a),y(b) {} 45 }; 46 47 template <class T> 48 struct vexNode 49 { 50 T data; 51 vexNode<T> *next; 52 vexNode(T d, vexNode<T> *n = NULL):data(d), next(n) {} 53 bool friend operator<(const vexNode &a, const vexNode &b) 54 { 55 int V = a.data; 56 int W = b.data; 57 int dV = vec[V].x * vec[V].x + vec[V].y * vec[V].y; 58 int dW = vec[W].x * vec[W].x + vec[W].y * vec[W].y; 59 return dV < dW; // 出队先出大的,再出小的 60 } 61 }; 62 63 /* 64 * 计算两点之间的距离 65 */ 66 double Distance(pos p1, pos p2, int dis) 67 { 68 double xx = (p1.x - p2.x) * (p1.x - p2.x); 69 double yy = (p1.y - p2.y) * (p1.y - p2.y); 70 if((p1.x == 0 && p1.y == 0) || (p2.x == 0 && p2.y == 0)) 71 { 72 return dis + FIRSTSTEP - sqrt(xx + yy); 73 } 74 else 75 { 76 return dis - sqrt(xx + yy); 77 } 78 } 79 80 /* 81 * 获得路径 82 */ 83 vector<int> getPath(int t, int p[]) 84 { 85 vector<int> path; 86 for(; t!=-1; t=p[t]) 87 path.push_back(t); 88 reverse(path.begin(),path.end()); 89 return path; 90 } 91 92 int main() 93 { 94 int nNum; 95 double dis; 96 cin >> nNum >> dis; 97 // 考虑特殊情况,能否一步迈出 98 if(dis + FIRSTSTEP >= BORDER) 99 { 100 cout << "1" << endl; 101 return 0; 102 } 103 // 起始点(小岛)也算一个点 104 vec.push_back(pos(0, 0)); 105 eVec.push_back(vexNode<int>(0)); 106 nNum ++; 107 // 用邻接表存储图 108 for(int i=1; i<nNum; i++) 109 { 110 double a, b; 111 cin >> a >> b; 112 vec.push_back(pos(a,b)); 113 eVec.push_back(vexNode<int>(i)); 114 } 115 // 开始建图 116 for(int i=0; i<nNum; i++) 117 { 118 for(int j=0; j<nNum; j++) 119 { 120 if(i != j) 121 { 122 if(Distance(vec[i], vec[j], dis) >= 0) 123 { 124 // 查一查有没有重复的 125 bool myIFlag = false; 126 vexNode<int> *p = &eVec[i]; 127 while(p -> next != NULL) 128 { 129 p = p -> next; 130 if(p -> data == j) 131 { 132 myIFlag = true; 133 break; 134 } 135 } 136 // 如果没有重复的,就插在最后边 137 if(myIFlag == false) 138 p -> next = new vexNode<int>(j); 139 140 // 因为是无向图,也就是双向图,所以另一侧也要插 141 bool myJFlag = false; 142 vexNode<int> *q = &eVec[j]; 143 while(q -> next != NULL) 144 { 145 q = q -> next; 146 if(q -> data == i) 147 { 148 myJFlag = true; 149 break; 150 } 151 } 152 // 如果没有重复的,就插在最后边 153 if(myJFlag == false) 154 q -> next = new vexNode<int>(i); 155 } 156 } 157 } 158 } 159 // 相关数据结构的申请 160 int *dist = new int[nNum]; 161 int *path = new int[nNum]; 162 priority_queue<vexNode<int> > myQueue; 163 164 // 算法开始 165 // 1. 在相同的最短路里找第一步最小的放入优先级队列中 166 167 vexNode<int> *p = &eVec[0]; 168 while(p -> next != NULL) 169 { 170 p = p -> next; 171 myQueue.push(eVec[p->data]); 172 path[p->data] = 0; 173 } 174 int flag = 1; // flag用来标记是否是第一次 175 int minDist; // minDist记录最小的dist值 176 177 // 2. 从岛屿开始,能到达的所有结点做循环 178 179 while(!myQueue.empty()) 180 { 181 // 2.1 初始化 182 for(int i=0; i<nNum; i++) 183 { 184 dist[i] = -1; 185 path[i] = -1; 186 } 187 // 2.2 从队列中弹出一个结点,从这个结点开始,借助另一个队列,进行BFS 188 vexNode<int> vN = myQueue.top(); 189 myQueue.pop(); 190 path[vN.data] = 0; // 从myQueue队列中取出的结点,parent一定为岛屿(0,0) 191 queue<int> bfsQueue; // 进行BFS所需要的队列 192 bfsQueue.push(vN.data); 193 dist[vN.data] = 0; // 初始的dist值为0 194 while(!bfsQueue.empty()) 195 { 196 int W = bfsQueue.front(); 197 bfsQueue.pop(); 198 // 2.3 判定是不是已经可以上岸了 199 if(fabs(vec[W].x-BORDER)<=dis || fabs(vec[W].x+BORDER)<=dis 200 ||fabs(vec[W].y-BORDER)<=dis ||(vec[W].y+BORDER)<=dis) 201 { 202 // 2.3.1 如果是第一次,更新minDist值,并记录路径 203 if(flag&&W!=0) 204 { 205 minDist = dist[W]; 206 flag = 0; 207 pathVec = getPath(W, path); 208 } 209 // 2.3.2 如果不是第一次,则比较minDist值与dist值,并更新路径 210 else if(W!=0 && dist[W] <= minDist) 211 { 212 minDist = dist[W]; 213 pathVec = getPath(W, path); 214 } 215 } 216 // 2.4 如果没有上岸,则将其邻接结点放入队列中,并更新dist与path的值 217 else 218 { 219 for(int i=1; i<=nNum; i++) 220 { 221 if(Distance(vec[W], vec[i], dis) >= 0 && dist[i] == -1) 222 { 223 bfsQueue.push(i); 224 dist[i]=dist[W]+1; 225 path[i]=W; 226 } 227 } 228 } 229 230 } 231 } 232 233 // 3. 输出最终结果 234 235 if(pathVec.size() == 0) 236 cout << "0" << endl; 237 else 238 { 239 // 3.1 因为我们把(0,0)也当成结点了,这里不用+1 240 cout << pathVec.size() << endl; 241 for(int i=0; i<pathVec.size(); i++) 242 // 3.2 因为我们把(0,0)也当成结点了,但是不能让它输出,所以特殊考虑 243 if(vec[pathVec[i]].x == 0 and vec[pathVec[i]].y == 0) 244 ; 245 else 246 cout << vec[pathVec[i]].x << " " << vec[pathVec[i]].y << endl; 247 } 248 return 0; 249 }
AC成果: