机试基本算法笔记--搜索

1.枚举
...................

2.广搜 B F S BFS BFS

不仅仅可以用在图论当中;

所谓广搜,就是在遍历解答树时使每次状态转移时扩展出尽可能多的新状态,并且按照各个状态出现的先后顺序依次扩展他们,表现为对解答树的层次遍历,先由根节点扩展出所有深度为1的结点,以此类推,且先被扩展的结点其深度不大于后被扩展的结点深度。但是,这样的搜索状态会非常多,比如每个结点会扩展6个新结点,那么仅走十步,状态数就会达到 1 0 6 10^{6} 106,因此必须采取相应措施来约束,即剪枝

剪枝就是减去不可能存在或者不是最优的子树。比如每个状态都有代价值,在搜索每条路径时,将其代价值与此时的最小代价值比较,如果前者大于后者,则减掉当前这条路径。
一般广搜需要搭配队列的使用。

例题:
题目描述:

I g n a t i u s Ignatius Ignatius被魔王抓走了,有一天魔王出差去了,这可是 I g n a t i u s Ignatius Ignatius逃亡的好机会.魔王住在一个城堡里,城堡是一个 A ∗ B ∗ C A*B*C ABC的立方体,可以被表示成 A A A B ∗ C B*C BC的矩阵,刚开始 I g n a t i u s Ignatius Ignatius被关在 ( 0 , 0 , 0 ) (0,0,0) (0,0,0)的位置,离开城堡的门在 ( A − 1 , B − 1 , C − 1 ) (A-1,B-1,C-1) (A1,B1,C1)的位置,现在知道魔王将在 T T T分钟后回到城堡 I g n a t i u s Ignatius Ignatius每分钟能从一个坐标走到相邻的六个坐标中的其中一个.现在给你城堡的地图,请你计算出 I g n a t i u s Ignatius Ignatius能否在魔王回来前离开城堡(只要走到出口就算离开城堡,如果走到出口的时候魔王刚好回来也算逃亡成功),如果可以请输出需要多少分钟才能离开,如果不能则输出 − 1 -1 1

输入:

输入数据的第一行是一个正整数K,表明测试数据的数量.每组测试数据的第一行是四个正整数 A , B , C A,B,C A,B,C T ( 1 &lt; = A , B , C &lt; = 50 , 1 &lt; = T &lt; = 1000 ) T(1&lt;=A,B,C&lt;= 50,1&lt;= T&lt;= 1000) T(1<=A,B,C<=50,1<=T<=1000),它们分别代表城堡的大小和魔王回来的时间.然后是A块输入数据(先是第 0 0 0块,然后是第 1 1 1块,第 2 2 2 . . . . .... ....)每块输入数据有 B B B行,每行有 C C C个正整数,代表迷宫的布局,其中 0 0 0代表路, 1 1 1代表墙。

输出:

对于每组测试数据,如果 I g n a t i u s Ignatius Ignatius能够在魔王回来前离开城堡,那么请输出他最少需要多少分钟,否则输出 − 1 -1 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) (x,y,t) ( x , y ) (x,y) (x,y)表示当前坐标, t t t表示从起点到当前位置所需时间。我们需要的目标状态为 ( d x , d y , T ) (dx,dy,T) (dx,dy,T),其中 ( d x , d y ) (dx,dy) (dx,dy) D D D(终点)所在点的坐标, T T T为所需时间。初始状态为 ( s x , s y , 0 ) (sx,sy,0) (sx,sy,0),其中 ( s x , s y ) (sx,sy) (sx,sy) S S S(起点)点所在的坐标。同样的,在深搜中也需要剪枝,我们不去理睬被我们判定不可能存在所需状态的子树,以期能够减少所需遍历的状态个数,避免不必要的超时。

还有一点,主人公没走一步时,其所在的位置坐标中,只有一个坐标分量发生 + 1 +1 +1 − 1 -1 1的改变,那么两个坐标分量和的奇偶性将发生变化,说白了,当主人公走过奇数步时,其坐标和的奇偶性与起点不同,走过偶数补时,其坐标和的奇偶性与起点一样。那么就是说如果起点坐标和与重点坐标和的奇偶性不同,那么其必须需要走奇数步,反之,必须走偶数步,如若不然,直接输出 N O NO 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值