穷极搜索主要包括两个方面:
1.深度优先搜索
2.广度优先搜索
一些基本的思想
1.递归函数
在函数中调用自己的函数就是递归函数,例如阶乘函数可以定义为:
int fact(int n)
{
if (n == 0) return 1;return n * fact(n - 1);
}
递归最重要的一点就是函数的停止条件,在上面的例子中,当传入的参数为0时,就停止递归。
假如一个函数递归没有终结条件,那么将会反复调用自身,直到栈溢出。
斐波那契数列的例子如下:
int fib(int n)
{
if ( n <= 1) return n;return fib(n - 1) + fib(n - 2);
}
注意到,在这个递归调用时, x一些情况被重复计算了,所以如果能有一张表记录以前的计算结果,那么再次运行时就不需要重新计算,将会省去大量时间。
改写后的程序如下:
int memo[MAX_N+1];
int fib(int n)
{
if ( n <=1 ) return n;if(memo[n] != 0) return memo[n];return memo[n] = fib(n - 1) + fib(n - 2);
}
2.栈
先进后出的数据结构,可以使用C++标准库中的stack实现
一些操作的例子如下:
stack<int> s;
s.push(1);
s.top();
s.pop();
3.队列
先进先出的数据结构,可以使用C++标准库中的queue实现
一些操作的例子如下:
queue<int> que;
que.push(1);
que.front();
que.pop();
深度优先搜索(DFS)
深度优先主要通过状态的转移不断的寻找下一个状态,知道不能够继续转移了,则向前回溯一个状态,查看有没有其他状态可以转移,知道搜索完所有状态。
例子:部分和问题
题目要求:给定整数a1,a2...an,判断是否可以选择若干个数,使其和恰好为k
例如:
输入 n=4 a={1,2,4,7} k=13
输出 yes(13 = 2 + 4 + 7)
输入 n=4 a={1,2,4,7} k=15
输出 no
分析:这里最直接的方法就是通过试试所有的方案看看加起来有没有等于k的情况,但难点就在于怎么去枚举所有的情况,这里需要一些小技巧:我们发现,对于每一个ai,只有两种选择,加上或者不加上,那么就可以将所有的输入组织成一棵二叉树,每一个节点代表当前的数字现在所有的和是多少,左边节点代表不加上当前的数字,右边节点代表加上这个数字。这样,只要遍历整棵树,就可以了解所有情况。
代码如下:
int a[MAX_N];
int n, k;
/*计算第i个数的时候此时总和为sum,此时就有两种选择,加上i或者不加i,分别使用两次递归*/
bool dfs(int i,int sum)
{
if(i == n) return sum = k;if(dfs(i + 1, sum)) return true;if(dfs(i + 1, sum + a[i])) return true;return false;
}
void solve()
{
if(dfs(0, 0)) printf("yes");else printf("no");
}
广度优先搜索(BFS)
广度优先总是搜索距离初始状态近的状态,并由近至远依次搜索其他的状态,直到搜索完毕
在数据结构上,使用队列可以很方便的完成广度搜索的方法,首先将距离初始状态最近的状态如队列,然后从队列中不断的取出状态,然后把取出的状态可以转移到的其他状态如队列,如此往复,直到队列为空。
例子:迷宫的最短路径
题目要求:给定一个大小为N*M的迷宫,迷宫由通道和墙壁组成,每一步可以向邻接的上下左右4格作出移动,求从起点到终点移动的最少步数。
例如:
输入: #S######.#
......#..#
.#.##.##.#
.#........
##.##.####
....#....#.#######.#....#......####.###.....#...G#
输出:22
分析:无论深度优先还是广度优先,都涉及到了状态的转移,在迷宫问题中,每个状态就是所在位置的坐标,所以使用pair来表示状态。转移的方向为4个方向。在搜索的过程中,只需要将访问的距离
代码如下:
const int INF = 1000000;
typedef pair<int, int> P; //迷宫的坐标使用pair表示
char maze[MAX_N][MAX_M+1]; //表示迷宫的字符串数组
int N, M;
int sx, sy; //起点坐标
int gx, gy; //终点坐标
int d[MAX_N][MAX_M]; //到各个位置距离最短的数组
/*四个方向的表示,注意这里使用了两个数组,这样在后面可以使用一个循环就简单的访问四个方向*/
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
int bfs()
{
queue<P> que;/*所有位置初始化为INF*/for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++) d[i][j] = INF;
/*将起点入队列,并将距离设置为0*/que.push(P(sx, sy));d[sx][sy] = 0;
while (que.size()){
/*从队列最前端取出元素*/
P p = que.front(); que.pop();/*如果已经是终点,停止搜索*/if (p.first == gx && p.second == gy) break;
/*四个方向的循环*/for (int i = 0; i < 4; i++){
/*新的坐标位置*/
int nx = p.first + dx[i], ny = p.second + dy[i];
/*判断是否可以移动或者或者已经访问过了*/if (0 <= nx && nx < N &&0 <=ny &&ny < M && maze[nx][ny] != '#' && d[nx][ny] == INF){
/*可以移动的话将其入队列,并将该位置确定为p位置+1*/
que.push(P(nx, ny));d[nx][ny] = d[p.first][p.second] + 1;
}
}
}return d[gx][gy];
}
void solve()
{
int res = bfs();printf("%d\n", res);
}