LeetCode-BFS(广度优先搜索)总结

适用场景
输入数据:没什么特征,不像深搜,需要有“递归”的性质。如果是树或者图,概率更大。
状态转换图:树或者图。
求解目标:求最短。
思考的步骤
1. 是求路径长度,还是路径本身(或动作序列)?
(a) 如果是求路径长度,则状态里面要存路径长度(或双队列+ 一个全局变量)
(b) 如果是求路径本身或动作序列
i. 要用一棵树存储宽搜过程中的路径
ii. 是否可以预估状态个数的上限?能够预估状态总数,则开一个大数组,用树的双亲
表示法;如果不能预估状态总数,则要使用一棵通用的树。这一步也是第4 步的必
要不充分条件。
2. 如何表示状态?即一个状态需要存储哪些些必要的数据,才能够完整提供如何扩展到下一步
状态的所有信息。一般记录当前位置或整体局面。
3. 如何扩展状态?这一步跟第2 步相关。状态里记录的数据不同,扩展方法就不同。对于固定
不变的数据结构(一般题目直接给出,作为输入数据),如二叉树,图等,扩展方法很简单,直接往下一层走,对于隐式图,要先在第1 步里想清楚状态所带的数据,想清楚了这点,那如何扩展就很简单了。

4. 关于判重,状态是否存在完美哈希方案?即将状态一一映射到整数,互相之间不会冲突。
(a) 如果不存在,则需要使用通用的哈希表(自己实现或用标准库,例如unordered_set)
来判重;自己实现哈希表的话,如果能够预估状态个数的上限,则可以开两个数组,head
和next,表示哈希表,参考第§??节方案2。
(b) 如果存在,则可以开一个大布尔数组,作为哈希表来判重,且此时可以精确计算出状态
总数,而不仅仅是预估上限。
5. 目标状态是否已知?如果题目已经给出了目标状态,可以带来很大便利,这时候可以从起始
状态出发,正向广搜;也可以从目标状态出发,逆向广搜;也可以同时出发,双向广搜。

代码模板

广搜需要一个队列,用于一层一层扩展,一个hashset,用于判重,一棵树(只求长度时不需
要),用于存储整棵树。
对于队列,可以用queue,也可以把vector 当做队列使用。当求长度时,有两种做法:
1. 只用一个队列,但在状态结构体state_t 里增加一个整数字段step,表示走到当前状态用
了多少步,当碰到目标状态,直接输出step 即可。这个方案,可以很方便的变成A* 算法,
把队列换成优先队列即可。
2. 用两个队列,current, next,分别表示当前层次和下一层,另设一个全局整数level,表
示层数(也即路径长度),当碰到目标状态,输出level 即可。这个方案,状态可以少一个字
段,节省内存。
对于hashset,如果有完美哈希方案,用布尔数组(bool visited[STATE_MAX] 或vector<bool> visited(STATE_MAX, false)) 来表示;如果没有,可以用STL 里的set 或unordered_set。
对于树,如果用STL,可以用unordered_map<state_t, state_t > father 表示一颗树,代码非
常简洁。如果能够预估状态总数的上限(设为STATE_MAX),可以用数组(state_t nodes[STATE_-MAX]),即树的双亲表示法来表示树,效率更高,当然,需要写更多代码。
双队列的写法

001 /** 状态 */
002 struct state_t {
003     int data1;  /** 状态的数据,可以有多个字段. */
004     int data2;  /** 状态的数据,可以有多个字段. */
005     // dataN;   /** 其他字段 */
006     int action; /** 由父状态移动到本状态的动作,求动作序列时需要. */
007     int count;  /** 所花费的步骤数(也即路径长度-1),求路径长度时需要;
008                     不过,采用双队列时不需要本字段,只需全局设一个整数 */
009     bool operator==(const state_t &other) const {
010         return true;  // 根据具体问题实现
011     }
012 };
013  
014 // 定义hash函数
015  
016 // 方法1:模板特化,当hash函数只需要状态本身,不需要其他数据时,用这个方法比较简洁
017 namespace std {
018 template<> struct hash<state_t> {
019     size_t operator()(const state_t & x) const {
020         return 0; // 根据具体问题实现
021     }
022 };
023 }
024  
025 // 方法2:函数对象,如果hash函数需要运行时数据,则用这种方法
026 class Hasher {
027 public:
028     Hasher(int _m) : m(_m) {};
029     size_t operator()(const state_t &s) const {
030         return 0; // 根据具体问题实现
031     }
032 private:
033     int m; // 存放外面传入的数据
034 };
035  
036 /**
037  * @brief 反向生成路径.
038  * @param[in] father 树
039  * @param[in] target 目标节点
040  * @return 从起点到target的路径
041  */
042 template<typename state_t>
043 vector<state_t> gen_path(const unordered_map<state_t, state_t> &father,
044         const state_t &target) {
045     vector<state_t> path;
046     path.push_back(target);
047  
048     for (state_t cur = target; father.find(cur) != father.end();
049             cur = father.at(cur))
050         path.push_back(cur);
051  
052     reverse(path.begin(), path.end());
053  
054     return path;
055 }
056  
057 /**
058  * @brief 广搜.
059  * @param[in] state_t 状态,如整数,字符串,一维数组等
060  * @param[in] start 起点
061  * @param[in] grid 输入数据
062  * @return 从起点到目标状态的一条最短路径
063  */
064 template<typename state_t>
065 vector<state_t> bfs(const state_t &start, const vector<vector<int>> &grid) {
066     queue<state_t> next, current; // 当前层,下一层
067     unordered_set<state_t> visited; // 判重
068     unordered_map<state_t, state_t> father; // 树
069  
070     int level = 0;  // 层次
071     bool found = false// 是否找到目标
072     state_t target; // 符合条件的目标状态
073  
074     // 判断当前状态是否为所求目标
075     auto state_is_target = [&](const state_t &s) {return true; };
076     // 扩展当前状态
077     auto state_extend = [&](const state_t &s) {
078         vector<state_t> result;
079         // ...
080         return result;
081     };
082  
083     current.push(start);
084     visited.insert(start);
085     while (!current.empty() && !found) {
086         ++level;
087         while (!current.empty() && !found) {
088             const state_t state = current.front();
089             current.pop();
090             vector<state_t> new_states = state_extend(state);
091             for (auto iter = new_states.cbegin();
092                     iter != new_states.cend() && ! found; ++iter) {
093                 const state_t new_state(*iter);
094  
095                 if (state_is_target(new_state)) {
096                     found = true//找到了
097                     target = new_state;
098                     father[new_state] = state;
099                     break;
100                 }
101  
102                 next.push(new_state);
103                 // visited.insert(new_state); 必须放到 state_extend()里
104                 father[new_state] = state;
105             }
106         }
107         swap(next, current); //!!! 交换两个队列
108     }
109  
110     if (found) {
111         return gen_path(father, target);
112         //return level + 1;
113     else {
114         return vector<state_t>();
115         //return 0;
116     }
117 }

只用一个队列的写法
双队列的写法,当求路径长度时,不需要在状态里设置一个count 字段记录路径长度,只需全
局设置一个整数level,比较节省内存;只用一个队列的写法,当求路径长度时,需要在状态里设
置一个count 字段,不过,这种写法有一个好处——可以很容易的变为A* 算法,把queue 替换为
priority_queue 即可。

01 // 与模板1相同的部分,不再重复
02 // ...
03  
04 /**
05  * @brief 广搜.
06  * @param[in] state_t 状态,如整数,字符串,一维数组等
07  * @param[in] start 起点
08  * @param[in] grid 输入数据
09  * @return 从起点到目标状态的一条最短路径
10  */
11 template<typename state_t>
12 vector<state_t> bfs(state_t &start, const vector<vector<int>> &grid) {
13     queue<state_t> q; // 队列
14     unordered_set<state_t> visited; // 判重
15     unordered_map<state_t, state_t> father; // 树
16  
17     int level = 0;  // 层次
18     bool found = false// 是否找到目标
19     state_t target; // 符合条件的目标状态
20  
21     // 判断当前状态是否为所求目标
22     auto state_is_target = [&](const state_t &s) {return true; };
23     // 扩展当前状态
24     auto state_extend = [&](const state_t &s) {
25         vector<state_t> result;
26         // ...
27         return result;
28     };
29  
30     start.count = 0;
31     q.push(start);
32     visited.insert(start);
33     while (!q.empty() && !found) {
34         const state_t state = q.front();
35         q.pop();
36         vector<state_t> new_states = state_extend(state);
37         for (auto iter = new_states.cbegin();
38                 iter != new_states.cend() && ! found; ++iter) {
39             const state_t new_state(*iter);
40  
41             if (state_is_target(new_state)) {
42                 found = true//找到了
43                 target = new_state;
44                 father[new_state] = state;
45                 break;
46             }
47  
48             q.push(new_state);
49             // visited.insert(new_state); 必须放到 state_extend()里
50             father[new_state] = state;
51         }
52     }
53  
54     if (found) {
55         return gen_path(father, target);
56         //return level + 1;
57     else {
58         return vector<state_t>();
59         //return 0;
60     }
61 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值