广度优先搜索
BFS算法思想
- 二叉树的层次遍历是一种特殊的广度优先遍历.
- 它的基本思想是:
首先访问起始顶点v,接着由v的节点出发,访问v的各个未访问过的邻接顶点w1,w2…wi…,再从这些访问过的节点访问…访问他们未访问过的邻接顶点.直到所有节点都被访问. - 算法必须借助一个辅助队列
应用
使用场景
- 输入数据: 没有什么特征. 不像深度搜索.有递归的特性.若是树和图.使用BFS概率更高
- 状态转移: 树或有向无环图(DAG图)
- 求解目标: 最短路径
思考步骤
一. 是求路径长度,还是路径本身(或者动作序列)?
- 若是求路径长度,则状态里面要存路径的长度.
- 若是要求路径或者动作序列
- 要用一棵树存储宽搜过程中的路径
- 是否可以预估状态的个数的上限?
能预估状态总数,则开一个大数组,用树的双亲表示法;
若不能预估,则要使用一棵通用的树.
二. 如何表示状态?
- 即一个状态需要存储哪些必要的数据.才能完整地提供扩展下一步状态的所有信息
三. 如何扩展状态?
- 这一步与第二步有关.
- 对于隐式图! 要在第一步想清楚状态所带的数据.
四. 如何判断重复?
- 如果状态转移是一颗树! 不会出现回路, 不需要判重!
- 若是一个图. 需要判重!
- 若果是求最短路径或一条路径. 只需要让点不重复!!! (如删除这些状态)?
- 若是求所有路径.注意,此时状态转移图是
有向无环图
.即允许两个父节点指向同一个子节点. 具体实现时,每个节点需要 延迟 加入已访问集合visited
,要等一层全部访问后,再加入到visited
集合 - 具体实现:
- 状态是否存在完美的哈希方案? 即将状态一一映射到整数,互相之间不会冲突.
- 如不存在,则需要使用通用的 哈希表来判重.
五. 目标状态是否已知?
- 若给出目标状态.可以带来很大遍历.
LeetCode 127 Word Ladder
给定你两个单词,要你从begin那个变换成end那个
要求变换过去改变最小的次数.
每次只能变换一次.
而且变换过程出现的单词,必须出现在给定的字典中
Return 0 if there is no such transformation sequence.
方法1:
/*
广度优先搜索
1. 从头开始遍历,尝试改变i位置的值
2. 若存在于worldlist则进行判断.
a) 若为endWord,则可以返回了
b) 不是endWord,将中间过程加入Map中.
3. 不存在于Wordlist...
*/
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
set<string> WordSet(wordList.begin(), wordList.end()); // 存储字典,防止重复
if (!WordSet.count(endWord)) return 0;
unordered_map<string, int> WordMap; // 用来存储过程的单词以及路径的长度
WordMap.insert(make_pair(beginWord, 1));
queue<string> q;
q.push(beginWord);
while (!q.empty()) {
string word = q.front(); q.pop();
// 从0开始,BFS
for (int i = 0; i < word.size(); ++i) {
string newWord = word;
// 尝试更换字符
for (char c = 'a'; c <= 'z'; ++c) {
newWord[i] = c;
if (WordSet.count(newWord) && newWord == endWord) {
return WordMap[word] + 1;
}
// 存在于字典中,但是不在路径中
if (WordSet.count(newWord) && !WordMap.count(newWord)) {
q.push(newWord);
WordMap[newWord] = WordMap[word] + 1;
}
}
}
}
return 0;
}
};
方法2:
- 同样是广度优先搜索
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
set<string> WordSet(wordList.begin(), wordList.end()); // 存储字典,防止重复
if (!WordSet.count(endWord)) return 0;
int res = 0;
queue<string> q;
q.push(beginWord);
while (!q.empty()) {
// size指的是相同层的,从后面遍历,减少时间
for (int i = q.size(); i > 0; --i) {
string word = q.front(); q.pop();
if (word == endWord) {
return res + 1;
}
for (int j = 0; j < word.size(); ++j) {
string newWord = word;
for (char c = 'a'; c <= 'z'; ++c) {
newWord[j] = c;
// 若字典中存在
if (WordSet.count(newWord)/*&&newWord!=word*/) {
q.push(newWord);
WordSet.erase(newWord);
}
}
}
}
res++;
}
return 0;
}
};
LeetCode 126 Word Ladder II
找到从start到end的所有最短路径
方法1:
- 广度优先搜索+深度优先搜索
- 超时!
- 使用BFS,找到最短的路径,记录从start到end的节点,保存每个节点的下一层节点到MAP中
- 使用DFS,从MAP中输出和最短路径相同距离的距离.
class Solution {
public:
vector < vector<string>> res;
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
unordered_map<string, vector<string>> next; // 记录下一层
unordered_map<string, int> distance; // 用于记录距离
unordered_set<string> dict(wordList.begin(), wordList.end());
queue<string> q; // 遍历bfs使用
vector<string> path;
// bfs
q.push(beginWord);
distance[beginWord] = 0;
while (!q.empty()) {
string cur = q.front(); q.pop();
if (cur == endWord) {
break;
}
int cur_dis = distance[cur];
vector<string> cur_next;
for (int i = 0; i < cur.size(); ++i) {
string newWord = cur;
for (auto c = 'a'; c <= 'z'; ++c) {
newWord[i] = c;
// 找不到就下一轮
if (c == cur[i] || dict.find(newWord) == dict.end()) continue;
auto it = distance.find(newWord);
if (it == distance.end()) {
// 找不到就要将这个带加入cur_next
q.push(newWord);
distance[newWord] = cur_dis + 1;
}
cur_next.push_back(newWord);
}
}
next[cur] = cur_next;
}
path.push_back(beginWord);
dfs(path, next, distance, beginWord, endWord);
return res;
}
void dfs(vector<string> &path, unordered_map<string, vector<string>> &next,
unordered_map<string, int> distance, string cur, string end) {
if (cur == end) {
res.push_back(path);
}
else {
auto vec = next[cur];
int cur_dis = distance[cur];
for (int i = 0; i < vec.size(); i++) {
if (distance[vec[i]] != cur_dis + 1)continue;
path.push_back(vec[i]);
dfs(path, next, distance, vec[i], end);
path.pop_back();
}
}
}
};
方法2:
class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
vector<vector<string>> res;
// 查询字典
unordered_set<string> dict(wordList.begin(), wordList.end());
vector<string> p = { beginWord };
queue<vector<string>> paths;
paths.push(p); // 插入以开始词为节点的路径
//level是记录循环中当前路径的长度
//minLevel记录的最短的记录
int level = 1, minLevel = INT_MAX;
// 定义一个set变量words,用来记录已经出现过的路径中的词
unordered_set<string> words;
while (!paths.empty()) {
auto t = paths.front(); paths.pop(); // 取出一条路径
// 剪枝
if (t.size() > level) {
// 将已经在路径中出现过的字符串在字典中删除
for (string w : words) {
dict.erase(w);
}
words.clear();
// 更新当前路径的长度
level = t.size();
// 如果当前路径的长度大于最小的距离说明遍历结束
if (level > minLevel) break;
}
string last = t.back();
for (int i = 0; i < last.size(); ++i) {
string newLast = last; // 对last进行扩展
for (char c = 'a'; c <= 'z'; ++c) {
newLast[i] = c;
if(!dict.count(newLast)) continue; // 若在字典中找不到,继续扩展
// words加入新词,说明这个词遍历过
words.insert(newLast);
// 原有路径的基础上加上这个新词生成一条新路径
vector<string> newPath = t;
newPath.push_back(newLast);
if (newLast == endWord) {
res.push_back(newPath);
minLevel = level; // 为什么不需要比较呢?因为上面有剪枝
} else {
// 还没有到最后一个词,就将这条路径继续加入paths
paths.push(newPath);
}
}
}
}
return res;
}
};