搜索本质就是以某种特定的方法,枚举状态空间的状态。如果搜索空间是线性的,通常直接枚举
1
到n
的正整数,每个整数都是一个状态搜索的过程其实就是,从一个起始状态出发,通过给定的规则寻找后继状态,到达终止状态时停止继续扩展
以起点状态为根,每个状态向其后继状态连有向边,可以得到一棵有根数,终止状态对应这棵树的叶子节点。搜索过程可以被抽象成遍历这棵搜索树的过程,如果需要遍历整棵树,则复杂度至少正比于搜索树的节点数量。
Q
,终止状态的集合为F
O(|Q|) = O(|F|)
k
层,每个非终止状态恰好有d
个后继,那么终止状态的数量为|F| = d^k
O(c)
,则搜索的总复杂度为O(c|Q|) = O(cd^k)
k
)是指数级增长的1
种状态1+6 = 7
种状态7+6*6 = 42
种状态40
次的结果要算51
万年我们先定一简单的节点结构
struct Node {
int val;
std::vector<Node *> children;
Node(int v) { val = v; };
};Ï
优先遍历一个后继节点的子树内所有节点,更加通俗的理解就是,先一条路走到黑,再返回上一个分岔节点
void dfs(Node *node) {
if (!node) return;
std::cout << node->val << std::endl;
for (int i = 0; i < node->children.size(); ++i) {
dfs(node->children[i]);
}
}
如果节点是空,表明父节点是终止状态,因此停止此分支的搜索。如果节点不为空,表明节点存在后继状态,因此以某种顺序递归调用dfs
即可
先遍历所有后继节点,再遍历后继节点的后继,更加通俗的理解就是,在分岔点分身,最总每个终止节点都有一个分身
void bfs(Node *node) {
if (!node) return;
std::queue<Node *> q;
q.push(node);
while (!q.empty()) {
Node *n = q.front();
std::cout << n->val << std::endl;
for (int i = 0; i < n->children.size(); ++i) {
q.push(n->children[i]);
}
q.pop();
}
}
先使用一个队列保存状态,如果队列不为空,即对队列中的第一个状态做操作,然后将这个状态的所有后继状态全部加入到队列中,这个队列保证了只有当前层k
上的节点和k+1
层的节点。
DFS | BFS |
---|---|
只需要存储从初始状态到当前状态的一条路径 | 需要存储所有尚待拓展的状态,空间开销大 |
当递归层数比较深时可能出现爆栈 | 可以动态使用堆内存 |
需要考虑回溯撤销的问题,细节可能比较麻烦 | 状态单向拓展,实现较为简单 |
搜索层数不确定时可能带来问题:无限拓展 | 可以知道从初始状态到每个状态的最少步数 |
子树中节点编号是连续的 | 同一层的节点编号是连续的 |
通过上面我们知道,使用DFS
搜索解时,有可能遇到层数不确定的问题。但是有时候由于空间限制等种种原因不适合使用BFS
,但是又需要们求解出最小层数的解。这个时候我们可以考虑迭代加深搜索
对于迭代加深搜索,首先深度优先搜索k
层,若没有找到可行解,再深度优先搜索k+1
层,直到找到可行解为止。由于深度是从小到大逐渐增大的,所以当搜索到结果时可以保证搜索深度是最小的。这也是迭代加深搜索在一部分情况下可以代替广度优先搜索的原因。
迭代加深搜索的优势大体可归纳为以下三点
k+1
层时会重复搜索k
层,但是整体而言并不比广搜慢很多剪枝的目的是让效率更高,但是注意不要将最优解给剪掉
给定一个国际象棋棋盘,要求在上面放置N
个皇后,使得两两之间不能相互攻击(皇后可以对同行,同列,同一对角线的棋子进行攻击)。这是一个典型的DFS
案例
8
个格子,终止状态是64*63*62*...*57= 4426165368
8!=40320
个状态bool check(std::vector<std::string> &rst, int i, int j) {
for (int k = 0; k < i; ++k) {
if (rst[k][j] == 'Q') return false;
}
int m = i - 1, n = j - 1;
while (m >= 0 && n >= 0) {
if (rst[m][n] == 'Q') return false;
--m;
--n;
}
m = i - 1, n = j + 1;
while (m >= 0 && n < rst[i].size()) {
if (rst[m][n] == 'Q') return false;
--m;
++n;
}
return true;
}
void dfs(std::vector<std::vector<std::string>> &all_solve, std::vector<std::string> &solve, int n, int k) {
if (n == k) {
all_solve.push_back(solve);
return;
}
for (int i = 0; i < n; ++i) {
if (check(solve, k, i)) {
solve[k][i] = 'Q';
dfs(all_solve, solve, n, k + 1);
solve[k][i] = '.';
}
}
}
std::vector<std::vector<std::string>> solve_n_queens(int n) {
std::vector<std::vector<std::string>> rst;
std::vector<std::string> solve(n, std::string(n, '.'));
dfs(rst, solve, n, 0);
return rst;
}