@@ -8,7 +8,7 @@ \chapter{广度优先搜索}
8
8
9
9
10
10
\section {Word Ladder } % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
11
- \label {sec:wordladder }
11
+ \label {sec:word-ladder }
12
12
13
13
14
14
\subsubsection {描述 }
@@ -95,12 +95,12 @@ \subsubsection{代码}
95
95
\subsubsection {相关题目 }
96
96
97
97
\begindot
98
- \item Word Ladder II,见 \S \ref {sec:wordladderii }
98
+ \item Word Ladder II,见 \S \ref {sec:word-ladder-ii }
99
99
\myenddot
100
100
101
101
102
102
\section {Word Ladder II } % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
103
- \label {sec:wordladderii }
103
+ \label {sec:word-ladder-ii }
104
104
105
105
106
106
\subsubsection {描述 }
@@ -212,5 +212,228 @@ \subsubsection{代码}
212
212
\subsubsection {相关题目 }
213
213
214
214
\begindot
215
- \item Word Ladder,见 \S \ref {sec:wordladder }
215
+ \item Word Ladder,见 \S \ref {sec:word-ladder }
216
216
\myenddot
217
+
218
+
219
+ \section {Surrounded Regions } % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
220
+ \label {sec:surrounded-regions }
221
+
222
+
223
+ \subsubsection {描述 }
224
+ Given a 2D board containing \fn {'X'} and \fn {'O'}, capture all regions surrounded by \fn {'X'}.
225
+
226
+ A region is captured by flipping all \fn {'O'}s into \fn {'X'}s in that surrounded region .
227
+
228
+ For example,
229
+ \begin {Code }
230
+ X X X X
231
+ X O O X
232
+ X X O X
233
+ X O X X
234
+ \end {Code }
235
+
236
+ After running your function, the board should be:
237
+ \begin {Code }
238
+ X X X X
239
+ X X X X
240
+ X X X X
241
+ X O X X
242
+ \end {Code }
243
+
244
+
245
+ \subsubsection {分析 }
246
+ 广搜。从上下左右四个边界往里走,凡是能碰到的\fn {'O'},都是跟边界接壤的,应该删除。
247
+
248
+
249
+ \subsubsection {代码 }
250
+ \begin {Code }
251
+ // LeetCode, Surrounded Regions
252
+ // BFS
253
+ class Solution {
254
+ public:
255
+ void solve(vector<vector<char>> &board) {
256
+ if (board.size() == 0) return;
257
+
258
+ const int m = board.size();
259
+ const int n = board[0].size();
260
+ for (int i = 0; i < n; i++) {
261
+ bfs(board, 0, i);
262
+ bfs(board, m - 1, i);
263
+ }
264
+ for (int j = 1; j < m - 1; j++) {
265
+ bfs(board, j, 0);
266
+ bfs(board, j, n - 1);
267
+ }
268
+ for (int i = 0; i < n; i++)
269
+ for (int j = 0; j < m; j++)
270
+ if (board[i][j] == 'O' )
271
+ board[i][j] = 'X' ;
272
+ else if (board[i][j] == '+' )
273
+ board[i][j] = 'O' ;
274
+ }
275
+ private:
276
+ void bfs(vector<vector<char>> &board, int i, int j) {
277
+ queue<int> q;
278
+ visit(board, i, j, q);
279
+ while (!q.empty()) {
280
+ int cur = q.front(); q.pop();
281
+ const int x = cur / board[0].size();
282
+ const int y = cur % board[0].size();
283
+ visit(board, x - 1, y, q);
284
+ visit(board, x, y - 1, q);
285
+ visit(board, x + 1, y, q);
286
+ visit(board, x, y + 1, q);
287
+ }
288
+ }
289
+ void visit(vector<vector<char>> &board, int i, int j, queue<int> &q) {
290
+ const int m = board.size();
291
+ const int n = board[0].size();
292
+ if (i < 0 || i >= m || j < 0 || j >= n || board[i][j] != 'O' )
293
+ return;
294
+ board[i][j] = '+' ; // 既有标记功能又有去重功能
295
+ q.push(i * n + j);
296
+ }
297
+ };
298
+ \end {Code }
299
+
300
+
301
+ \subsubsection {相关题目 }
302
+
303
+ \begindot
304
+ \item 无
305
+ \myenddot
306
+
307
+
308
+ \section {小结 } % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
309
+ \label {sec:bfs-template }
310
+
311
+
312
+ \subsection {适用场景 }
313
+ 注意,这里的总结是一种经验,一种概率,不是绝对的结论!
314
+
315
+ \textbf {输入数据 }:没什么特征,不像深搜,需要有“递归”的性质。如果是树或者图,概率更大。
316
+
317
+ \textbf {状态转换图 }:树或者图。
318
+
319
+ \textbf {求解目标 }:求最短。
320
+
321
+
322
+ \subsection {思考的步骤 }
323
+ \begin {enumerate }
324
+ \item 是求路径长度,还是路径本身(或动作序列)?
325
+ \begin {enumerate }
326
+ \item 如果是求路径长度,则状态里面要存路径长度
327
+ \item 如果是求路径本身或动作序列
328
+ \begin {enumerate }
329
+ \item 要用一棵树存储宽搜过程中的路径
330
+ \item 是否可以预估状态个数的上限?能够预估状态总数,则开一个大数组,用树的双亲表示法;如果不能预估状态总数,则要使用一棵通用的树。这一步也是第4步的必要不充分条件。
331
+ \end {enumerate }
332
+ \end {enumerate }
333
+
334
+ \item 如何表示状态?即一个状态需要存储哪些些必要的数据,才能够完整提供如何扩展到下一步状态的所有信息。一般记录当前位置或整体局面。
335
+
336
+ \item 如何扩展状态?这一步跟第2步相关。状态里记录的数据不同,扩展方法就不同。对于固定不变的数据结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层走,对于隐式图,要先在第1步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。
337
+
338
+ \item 关于判重,状态是否存在完美哈希方案?即将状态一一映射到整数,互相之间不会冲突。
339
+ \begin {enumerate }
340
+ \item 如果不存在,则需要使用通用的哈希表(自己实现或用标准库,例如\fn {unordered_set})来判重;自己实现哈希表的话,如果能够预估状态个数的上限,则可以开两个数组,head和next,表示哈希表,参考第 \S \ref {subsec:eightDigits }节方案2。
341
+ \item 如果存在,则可以开一个大布尔数组,作为哈希表来判重,且此时可以精确计算出状态总数,而不仅仅是预估上限。
342
+ \end {enumerate }
343
+
344
+ \item 目标状态是否已知?如果题目已经给出了目标状态,可以带来很大便利,这时候可以从起始状态出发,正向广搜;也可以从目标状态出发,逆向广搜;也可以同时出发,双向广搜。
345
+ \end {enumerate }
346
+
347
+
348
+ \subsection {代码模板 }
349
+ 广搜需要一个队列,用于一层一层扩展,一个hashset,用于判重,一棵树(只求长度时不需要),用于存储整棵树。
350
+
351
+ 对于队列,可以用\fn {queue},也可以把\fn {vector}当做队列使用。当求长度时,有两种做法:
352
+ \begin {enumerate }
353
+ \item 只用一个队列,但在状态结构体\fn {state_t}里增加一个整数字段\fn {step},表示走到当前状态用了多少步,当碰到目标状态,直接输出\fn {step}即可。这个方案,可以很方便的变成A*算法,把队列换成优先队列即可。
354
+ \item 用两个队列,\fn {current, next},分别表示当前层次和下一层,另设一个全局整数\fn {level},表示层数(也即路径长度),当碰到目标状态,输出\fn {level}即可。这个方案,状态可以少一个字段,节省内存。
355
+ \end {enumerate }
356
+
357
+ 对于hashset,如果有完美哈希方案,用布尔数组(\fn {bool visited[STATE_MAX]}或\fn {vector<bool> visited(STATE_MAX, false)})来表示;如果没有,可以用STL里的\fn {set}或\fn {unordered_set}。
358
+
359
+ 对于树,如果用STL,可以用\fn {unordered_map<state_t, state_t > father}表示一颗树,代码非常简洁。如果能够预估状态总数的上限(设为STATE_MAX),可以用数组(\fn {state_t nodes[STATE_MAX]}),即树的双亲表示法来表示树,效率更高,当然,需要写更多代码。
360
+
361
+
362
+ \begin {Codex }[label=bfs_template1.cpp]
363
+ /**
364
+ * @brief 反向生成路径.
365
+ * @param[in] father 树
366
+ * @param[in] target 目标节点
367
+ * @return 从起点到target的路径
368
+ */
369
+ template<typename state_t>
370
+ vector<state_t> gen_path(const unordered_map<state_t, state_t> &father,
371
+ const state_t &target) {
372
+ vector<state_t> path;
373
+ path.push_back(target);
374
+
375
+ state_t cur = target;
376
+ while (father.find(cur) != father.end()) {
377
+ cur = father.at(cur);
378
+ path.push_back(cur);
379
+ }
380
+ reverse(path.begin(), path.end());
381
+
382
+ return path;
383
+ }
384
+
385
+ /**
386
+ * @brief 广搜.
387
+ * @param[in] state_t 状态,如整数,字符串,一维数组等
388
+ * @param[in] start 起点
389
+ * @param[in] state_is_target 判断状态是否是目标的函数
390
+ * @param[in] state_extend 状态扩展函数
391
+ * @return 从起点到目标状态的一条最短路径
392
+ */
393
+ template<typename state_t>
394
+ vector<state_t> bfs(state_t &start, bool (*state_is_target)(const state_t&),
395
+ vector<state_t>(*state_extend)(const state_t&,
396
+ unordered_set<string> &visited)) {
397
+ queue<state_t> next, current; // 当前层,下一层
398
+ unordered_set<state_t> visited; // 判重
399
+ unordered_map<state_t, state_t> father;
400
+
401
+ int level = 0; // 层次
402
+ bool found = false;
403
+ state_t target;
404
+
405
+ current.push(start);
406
+ while (!current.empty() && !found) {
407
+ ++level;
408
+ while (!current.empty() && !found) {
409
+ const state_t state = current.front();
410
+ current.pop();
411
+ vector<state_t> new_states = state_extend(state, visited);
412
+ for (auto iter = new_states.begin();
413
+ iter != new_states.end() && ! found; ++iter) {
414
+ const state_t new_state(*iter);
415
+
416
+ if (state_is_target(new_state)) {
417
+ found = true; //找到了
418
+ target = new_state;
419
+ father[new_state] = state;
420
+ break;
421
+ }
422
+
423
+ next.push(new_state);
424
+ // visited.insert(new_state); 必须放到 state_extend()里
425
+ father[new_state] = state;
426
+ }
427
+ }
428
+ swap(next, current); //!!! 交换两个队列
429
+ }
430
+
431
+ if (found) {
432
+ return gen_path(father, target);
433
+ //return level + 1;
434
+ } else {
435
+ return vector<state_t>();
436
+ //return 0;
437
+ }
438
+ }
439
+ \end {Codex }
0 commit comments