1.枚举
...................
2.广搜BFS
不仅仅可以用在图论当中;
所谓广搜,就是在遍历解答树时使每次状态转移时扩展出尽可能多的新状态,并且按照各个状态出现的先后顺序依次扩展他们,表现为对解答树的层次遍历,先由根节点扩展出所有深度为1的结点,以此类推,且先被扩展的结点其深度不大于后被扩展的结点深度。但是,这样的搜索状态会非常多,比如每个结点会扩展6个新结点,那么仅走十步,状态数就会达到106,因此必须采取相应措施来约束,即剪枝。
剪枝就是减去不可能存在或者不是最优的子树。比如每个状态都有代价值,在搜索每条路径时,将其代价值与此时的最小代价值比较,如果前者大于后者,则减掉当前这条路径。
一般广搜需要搭配队列的使用。
例题:
题目描述:
Ignatius被魔王抓走了,有一天魔王出差去了,这可是Ignatius逃亡的好机会.魔王住在一个城堡里,城堡是一个A∗B∗C的立方体,可以被表示成A个B∗C的矩阵,刚开始Ignatius被关在(0,0,0)的位置,离开城堡的门在(A−1,B−1,C−1)的位置,现在知道魔王将在T分钟后回到城堡Ignatius每分钟能从一个坐标走到相邻的六个坐标中的其中一个.现在给你城堡的地图,请你计算出Ignatius能否在魔王回来前离开城堡(只要走到出口就算离开城堡,如果走到出口的时候魔王刚好回来也算逃亡成功),如果可以请输出需要多少分钟才能离开,如果不能则输出−1。
输入:
输入数据的第一行是一个正整数K,表明测试数据的数量.每组测试数据的第一行是四个正整数A,B,C和T(1<=A,B,C<=50,1<=T<=1000),它们分别代表城堡的大小和魔王回来的时间.然后是A块输入数据(先是第0块,然后是第1块,第2块....)每块输入数据有B行,每行有C个正整数,代表迷宫的布局,其中0代表路,1代表墙。
输出:
对于每组测试数据,如果Ignatius能够在魔王回来前离开城堡,那么请输出他最少需要多少分钟,否则输出−1.
#include <bit/stdc++.h>
using namespace std;
struct Status {
int x, y, z, t;
};
int maze[50][50][50];
bool mark[50][50][50];
queue<Status> q;
int go[6][3] = {
1, 0, 0,
-1, 0, 0,
0, 1, 0,
0, -1, 0,
0, 0, 1,
0, 0, -1
};
int bfs(int a, int b, int c) {
while(!q.empty()) q.pop();
mark[0][0][0] = true;
Status status;
status.x = status.y = status.z = status.t = 0;
q.push(status);
while(!q.empty()) {
status = q.front();
q.pop();
for(int i = 0; i < 6; i++) {
Status tmp;
tmp.x = status.x + go[i][0];
tmp.y = status.y + go[i][1];
tmp.z = status.z + go[i][2];
tmp.t = status.t + 1;
if(tmp.x >= 0 && tmp.x < a &&tmp.y >= 0 && tmp.y < b && tmp.z >= 0 && tmp.z < c) {
if(maze [tmp.x][tmp.y][tmp.z] == 0) {
if(mark[tmp.x][tmp.y][tmp.z] == false) {
mark[tmp.x][tmp.y][tmp.z] = true;
q.push(tmp);
if(tmp.x == a-1 && tmp.y == b-1 && tmp.z == c-1)
return tmp.t;
}
}
}
}
}
return -1;
}
int main() {
int K;
scanf("%d", &K);
while(K--) {
int A, B, C, T;
scanf("%d%d%d%d", &A, &B, &C, &T);
for(int i = 0; i < A; i++) {
for(int j = 0; j < B; j++) {
for(int k = 0; k < C; k++) {
scanf("%d", &maze[i][j][k]);
mark[i][j][k] = false;
}
}
}
int res = bfs(A, B, C);
if(res == -1) printf("-1\n");
else if(res <= T) printf("%d\n", res);
else printf("-1\n");
}
return 0;
}
3.深搜
与广搜对应的是深搜,其优先遍历子结点。在广度优先搜索中,较早得到的状态较先得到扩展,因为队列是FIFO,而深搜反之,因为栈是先进后出,这是利用了队列和栈的特性。一提到栈就想到可以用递归和非递归两种方法实现。
深搜不再具有广搜中按层次递增顺序遍历的特性,所以当广搜搜索到我们需要的状态时,其不再具有某种最优的特性。因此深搜常用来解决 “有或没有” 的问题,即对解答书是否有我们需要的答案进行判定,而一般不使用深搜求解最优解。
例题:
英文题目,大致题意是:
有一个N*M的迷宫,包括起点S,终点D,墙X和地面,0秒时主人公从S出发,每秒能走到四个与其相邻的位置中的一个,且每个位置被行走之后不能再次走入,问是否存在这样一条路径使主人公在T秒时刚好到D。
输入样例:
4 4 5
S.X.
..X.
..XD
....
3 4 5
S.X.
..X.
...D
0 0 0
输出:
NO
YES
在这个问题中,题目不再要求求出最优解,而是需要我们判断是否存在一条符合条件的路径,所以用广搜来解决。
确定状态三元组(x,y,t),(x,y)表示当前坐标,t表示从起点到当前位置所需时间。我们需要的目标状态为(dx,dy,T),其中(dx,dy)为D(终点)所在点的坐标,T为所需时间。初始状态为(sx,sy,0),其中(sx,sy)为S(起点)点所在的坐标。同样的,在深搜中也需要剪枝,我们不去理睬被我们判定不可能存在所需状态的子树,以期能够减少所需遍历的状态个数,避免不必要的超时。
还有一点,主人公没走一步时,其所在的位置坐标中,只有一个坐标分量发生+1或−1的改变,那么两个坐标分量和的奇偶性将发生变化,说白了,当主人公走过奇数步时,其坐标和的奇偶性与起点不同,走过偶数补时,其坐标和的奇偶性与起点一样。那么就是说如果起点坐标和与重点坐标和的奇偶性不同,那么其必须需要走奇数步,反之,必须走偶数步,如若不然,直接输出NO。(有点绕,仔细读读还是很好理解的)
#include <bit/stdc++.h>
using namespace std;
char str[10];
char maze[10][10];
bool mark[10][10];
int go[4][2] = {
1,0,
-1,0,
0,1,
0,-1
};
bool solve(int i, int j, int time, int n, int m, int t) {
if(maze[i][j] == 'D' && time == t) return true;
for(int k = 0; k < 4; k++)
{
int row = i + go[k][0];
int col = j + go[k][1];
if(row >= 0 && row < n && col >= 0 && col < m)
{
if((maze[row][col] == '.' || maze[row][col] == 'D') && mark[row][col] == false)
{
mark[row][col] = true;
if(solve(row, col, time+1, n, m, t)) return true;
mark[row][col] = false;
}
}
}
return false;
}
int main() {
int n, m, t;
while(scanf("%d%d%d", &n, &m, &t) == 3)
{
if(n == 0 && m == 0 && t == 0) break;
int iS, jS;
for(int i = 0; i < n; i++)
{
scanf("%s", str);
for(int j = 0; j < m; j++)
{
maze[i][j] = str[j];
mark[i][j] = false;
if(maze[i][j] == 'S')
{
iS = i; jS = j;
}
}
}
printf("%s\n", solve(iS, jS, 0, n, m, t)? "YES":"NO");
}
return 0;
}
4.递归
也没啥可说的,基本思想很简单,但是想灵活并且很好地运用还是有难度的。主要注意它的出口条件和递归入口。在搜索中,需要一个变量去控制它的递归深度。主要应用有汉诺塔,约瑟夫环等。
汉诺塔代码
#include <iostream>
#include <cstdio>
using namespace std;
void hannoi (int n, char A, char B, char C) // 把A盘里面的圆圈转移到C盘里面【A--C】
{
if (n == 1)
cout << n << "从" << A << "到" << C << endl; //把最后一个圆环从起点盘移动到目标盘。
else
{
hannoi (n-1, A, C, B); // 把N-1个圆环从起点盘移动到(当前)没有任何圆环的过度盘;通过B、C盘在此函数调用中调用位置的互换,来实现把N-1个圆环从A盘到B盘的转移【A--B】。
cout << n << "从" << A << "到" << C << endl;
hannoi (n-1, B, A, C); // 把N-1个圆环从国度盘移动到目标盘(模仿1和2的操作方法来实现);通过A、B盘在此函数调用中位置的互换,来实现N-1个圆环从B盘到C盘的转移【B--C】。
}
}
int main()
{
int n;
cin >> n;
hannoi (n, 'a', 'b', 'c');
return 0;
}