广度优先搜索主要用了队列(queue)这种数据结构先进数据后出的性质
主要思想是将每一步搜索出的数据排成一个队列,这样排出来的队列可以保证同一深度的数据在队列的连续空间中线性排列从而可以保证每次可以同一批次处理相同的深度的所有数据(广度优先)从而能稳定的找到想要搜索的数据的深度
下面给出一个例题:
(题目来自:https://leetcode-cn.com/explore/learn/card/queue-stack/217/queue-and-bfs/873/)
(题目给出的答案模板)
class Solution {
public:
int openLock(vector& deadends, string target) {
}
};
考虑到这道题可以用广度优先搜索解决
考虑到直接操作字符串比较繁琐且代码可读性会下降很多于是设置两个内联函数
inline string Add(string num,int index)
{
num[i] = (num[i] - 47) % 10 + 48;
return num;
}
inline string Sub(string num,int index)
{
num[i] = (num[i] - 39) % 10 + 48;
return num;
}
这样间接达到向前拨和向后拨密码的效果
用广度优先搜索时我们需要一个节点队列queue< string > nodes用来保存每次搜索得到的结果
用一个集合set < string > used保存所有搜索后得到过的数据以防止有已经被搜索得到的数据多次进入队列nodes被重复搜索(如:可由深度为3的0340搜到0350,0330进入nodes深度为4,由深度为5的0250搜到0330,0330进入nodes深度为6,这就导致了深度4和深度6在搜索的时候都会搜索0330出现此情况可能会导致死循环的出现)
同时注意到题目中有不能被设置的密码deadends,我们可以把它理解成它已经在之前的搜索中出现过,在以后的搜索中不能出现这些数。于是我们可以把这些deadends合并到used中。开头代码就如下:
int openLock(vector& deadends, string target) {
string now = "0000";//初始密码
set used;
queue nodes;
for (int i = 0; i != deadends.size(); i++)
used.insert(deadends[i]);
}
当队列的长度为零时说明我们已经遍历了所有节点,将这个可以作为我们搜索循环的终止条件
接下来在每次对同一个深度进行搜索的时候,我们需要知道每次搜索时该深度一共有多少节点。于是在开头我们取一个变量获得队列长度,该长度就是该深度节点的个数
在对每个节点的搜索中,我们先将得到的结果放到used里去寻找,如果之前已经搜索到过该节点就跳过。如果不存在就将该节点作为下一次搜索的依据放到nodes队列里,同时防止下一次该节点被再次搜索将其放到used列表里
将上述思路综合一下就是:
int openLock(vector& deadends,string target)
{
....
int steps = 0;
while (!nodes.empty())
{
steps++;//深度加1
int size = nodes.size();//获得总节点数
for (int i = 0; i != size; i++)//遍历所有节点
{
now = nodes.front();
nodes.pop();
for (int i = 0; i != 4; i++)//遍历没个节点变化的所有可能情况
{
if (used.find(Add(now, i)) == used.end())//如果该变化不在列表里
{
if (Add(now, i) == target)//找到了
return steps;
else {
used.insert(Add(now, i));
nodes.push(Add(now, i));//没找到就把它放到下次搜索的队列里并且将它放到找到过的数据里
}
}
if (used.find(Sub(now, i)) == used.end())
{
if (Sub(now, i) == target)
return steps;
else {
used.insert(Sub(now, i));
nodes.push(Sub(now, i));
}
}
}
}
}
...
}
上述所有综合一下:
class Solution {
public:
int openLock(vector& deadends, string target) {
string now = "0000";
set used;
queue nodes;
for (int i = 0; i != deadends.size(); i++)
used.insert(deadends[i]);
if (used.find("0000") != used.end() || used.find(target) != used.end())
return -1;
nodes.push(now);
used.insert(now);
int steps = 0;
while (!nodes.empty())
{
steps++;
int size = nodes.size();
for (int i = 0; i != size; i++)
{
now = nodes.front();
nodes.pop();
for (int i = 0; i != 4; i++)
{
if (used.find(Add(now, i)) == used.end())
{
if (Add(now, i) == target)
return steps;
else {
used.insert(Add(now, i));
nodes.push(Add(now, i));
}
}
if (used.find(Sub(now, i)) == used.end())
{
if (Sub(now, i) == target)
return steps;
else {
used.insert(Sub(now, i));
nodes.push(Sub(now, i));
}
}
}
}
}
return -1;
}
private:
inline string Add(string num,int i)
{
num[i] = (num[i] - 47) % 10 + 48;
return num;
}
inline string Sub(string num,int i)
{
num[i] = (num[i] - 39) % 10 + 48;
return num;
}
};
放到主函数里测试一下
int main()
{
Solution solu;
while (true)
{
int size;
string target;
cin >> size >> target;
vector dead(size);
for (int i = 0; i != size; i++)
{
cin >> dead[i];
}
cout << solu.openLock(dead, target);
}
}