广度优先搜索尝试在不重复访问状态的情况下,寻找到一条最短路径。广度优先搜索保证如果存在一条到目标状态的路径,那么找到的肯定是最短路径。
事实上,深度优先搜索和广度优先搜索的唯一不同就是广度优先搜索使用队列来保存开放集,而深度优先搜索使用栈。每次迭代时,广度优先搜索从队列头拿出一个未访问的状态,然后从这个状态开始,计算后继状态。如果达到了目标状态,那么搜索结束。任何已经在闭合集中的后继状态将会被抛弃。剩余的未访问棋面状态将会放入开放集队列尾部,然后继续搜索。
使用如下状态作为八数码游戏的初始状态:
2 | 8 | 3 |
1 | 6 | 4 |
7 | 5 |
下图是计算出的搜索树,我们可以看到算法是如何在搜索所有4步长的路径之后,找到了一个5步长路径的解(几乎所有5步长的路径都被探测过)。图中20个灰黑色的棋面状态是在开放集中等待被搜索的。总共处理了25个棋面状态。
广度优先搜索也是在搜索树中盲目地搜索,在搜索可行的路径时会访问大量的节点。它虽然保证能够找到最短路径,但是也需要维护一个规模很大的开放集。不过,让我们感到宽心的是,由于使用的队列来存储开放集合,所以插入和删除操作能够在常数时间完成。
和深度优先搜索一样,广度优先搜索的性能和问题的共性和特性有关。深度优先搜索对于共性的分析同样适用于广度优先搜索,但是只是在开放集合的规模上有不同。广度优先搜索需要在开放集合中顺序存储bd个棋面状态,b是棋面状态的分支因子,d是找到的解的深度。开放集合的规模比深度优先搜索大得多,深度优先搜索的开放集合只需要存储b×d个棋面状态,即深度d时候的候选棋面状态数量。不过广度优先搜索能够保证找到一个解,从初始状态开始到目标状态,这个解的步数是最少的。
广度优先搜索是属于盲目的搜索,只有在搜索空间小于计算机内存空间时才具有可行性。因为广度优先搜索首先必须保证寻找到的是一条最短路径,所以,如果需要很多步才能得到解,那么所需时间会非常长。而且,这个算法可能不适合寻找从初始状态到目标状态的多条路径(例如我们并不需要寻找绝对最短路径)。
广度优先搜索在使用队列结构来存储状态的开放集合,每次从队列头中取出一个状态。闭合集用散列表结构存储。每个棋面状态都有一个回链,叫做过渡(Transition),这个链接记录的是得到当前棋面状态的走法以及前一个状态的引用。广度优先搜索多次复制一个棋面状态,用来尝试各种走法。
#include <iostream>
#include <queue>
#include <unordered_set>
#include <list>
#include <memory>
#include <vector>
#include <string>
#include <algorithm>
// Forward declarations
class IMove;
class INode;
class Solution;
class Transition;
// Interface for moves
class IMove {
public:
virtual ~IMove() = default;
virtual void execute(std::shared_ptr<INode> node) = 0;
virtual std::string getName() const = 0;
virtual std::unique_ptr<IMove> clone() const = 0;
};
// Interface for nodes
class INode {
public:
virtual ~INode() = default;
virtual bool equals(const std::shared_ptr<INode> other) const = 0;
virtual std::shared_ptr<INode> copy() const = 0;
virtual std::list<std::unique_ptr<IMove>> validMoves() const = 0;
virtual void storedData(std::shared_ptr<Transition> transition) = 0;
virtual std::shared_ptr<Transition> storedData() const = 0;
};
// Transition class to store move history
class Transition {
public:
Transition(std::shared_ptr<IMove> m, std::shared_ptr<INode> prevNode)
: move(m), previousNode(prevNode) {
}
std::shared_ptr<IMove> move;
std::shared_ptr<INode> previousNode;
};
// Solution class to store the result
class Solution {
public:
Solution(std::shared_ptr<INode> init, std::shared_ptr<INode> g, bool s = true)
: initial(init), goal(g), success(s) {
}
std::shared_ptr<INode> initial;
std::shared_ptr<INode> goal;
bool success;
std::vector<std::shared_ptr<IMove>> path;
};
// Concrete Move: Add 1
class AddOneMove : public IMove {
public:
void execute(std::shared_ptr<INode> node) override;
std::string getName() const override {
return "Add 1";
}
std::unique_ptr<IMove> clone() const override {
return std::make_unique<AddOneMove>();
}
};
// Concrete Move: Multiply by 2
class MultiplyTwoMove : public IMove {
public:
void execute(std::shared_ptr<INode> node) override;
std::string getName() const override {
return "Multiply by 2";
}
std::unique_ptr<IMove> clone() const override {
return std::make_unique<MultiplyTwoMove>();
}
};
// Concrete Node
class ConcreteNode : public INode, public std::enable_shared_from_this<ConcreteNode> {
public:
int value;
std::shared_ptr<Transition> data;
ConcreteNode(int val) : value(val), data(nullptr) {}
bool equals(const std::shared_ptr<INode> other) const override {
auto o = std::dynamic_pointer_cast<ConcreteNode>(other);
return o && (this->value == o->value);
}
std::shared_ptr<INode> copy() const override {
return std::make_shared<ConcreteNode>(value);
}
std::list<std::unique_ptr<IMove>> validMoves() const override {
std::list<std::unique_ptr<IMove>> moves;
moves.emplace_back(std::make_unique<AddOneMove>());
moves.emplace_back(std::make_unique<MultiplyTwoMove>());
return moves;
}
void storedData(std::shared_ptr<Transition> transition) override {
data = transition;
}
std::shared_ptr<Transition> storedData() const override {
return data;
}
};
// Implement execute for AddOneMove
void AddOneMove::execute(std::shared_ptr<INode> node) {
auto concreteNode = std::dynamic_pointer_cast<ConcreteNode>(node);
if (concreteNode) {
concreteNode->value += 1;
}
}
// Implement execute for MultiplyTwoMove
void MultiplyTwoMove::execute(std::shared_ptr<INode> node) {
auto concreteNode = std::dynamic_pointer_cast<ConcreteNode>(node);
if (concreteNode) {
concreteNode->value *= 2;
}
}
// Hash function for ConcreteNode to be used in unordered_set
struct NodeHash {
std::size_t operator()(const std::shared_ptr<INode>& node) const {
auto concreteNode = std::dynamic_pointer_cast<ConcreteNode>(node);
return concreteNode ? std::hash<int>()(concreteNode->value) : 0;
}
};
// Equality function for ConcreteNode to be used in unordered_set
struct NodeEqual {
bool operator()(const std::shared_ptr<INode>& lhs, const std::shared_ptr<INode>& rhs) const {
return lhs->equals(rhs);
}
};
// Main search function (Breadth-First Search)
Solution search(std::shared_ptr<INode> initial, std::shared_ptr<INode> goal) {
// If initial state is the solution, return
if (initial->equals(goal)) {
return Solution(initial, goal, true);
}
// Start from initial state
std::queue<std::shared_ptr<INode>> open;
open.push(initial->copy());
// States we've already visited
std::unordered_set<std::shared_ptr<INode>, NodeHash, NodeEqual> closed;
while (!open.empty()) {
std::shared_ptr<INode> current = open.front();
open.pop();
// Insert into closed set
closed.insert(current);
// Get all valid moves and process successors
std::list<std::unique_ptr<IMove>> moves = current->validMoves();
for (auto& move : moves) {
// Create a copy
std::shared_ptr<INode> successor = current->copy();
// Execute the move
move->execute(successor);
// Check if we've already visited this state
if (closed.find(successor) != closed.end()) {
continue;
}
// Store the transition information
std::shared_ptr<Transition> transition = std::make_shared<Transition>(move->clone(), current);
successor->storedData(transition);
// Check if we've found the goal
if (successor->equals(goal)) {
Solution sol(initial, successor, true);
// 回溯路径
std::shared_ptr<INode> step = successor;
while (step->storedData() != nullptr) {
sol.path.push_back(step->storedData()->move);
step = step->storedData()->previousNode;
}
// 反转路径以从初始状态到目标状态
std::reverse(sol.path.begin(), sol.path.end());
return sol;
}
// Add to open set
open.push(successor);
}
}
// No solution found
return Solution(initial, goal, false);
}
// Main function
int main() {
// 创建初始和目标节点
auto initial = std::make_shared<ConcreteNode>(1);
auto goal = std::make_shared<ConcreteNode>(10);
// 执行搜索
Solution solution = search(initial, goal);
// 检查是否找到解答
if (solution.success) {
std::cout << "找到解答!路径如下:" << std::endl;
for (const auto& move : solution.path) {
std::cout << move->getName() << " -> ";
}
auto concreteGoal = std::dynamic_pointer_cast<ConcreteNode>(solution.goal);
if (concreteGoal) {
std::cout << "达成目标 " << concreteGoal->value << std::endl;
}
}
else {
std::cout << "未找到解答。" << std::endl;
}
return 0;
}
1.《算法设计手册》