目录
深搜(DFS):
1.寻找路径
2.求连通块
广搜(BFS):
求最短路径
总结
例(详见洛谷P6207):如图,在有些地方不能通过的矩阵中找出从(1,1)坐标点到目标坐标点的路径并输出
'.'表示能通过,'*'表示不能通过
这种让我们寻找任意路径的题,我们可以轻松用深搜解决
代码:
#include
#include
using namespace std;
int r, c;
char a[120][80];//盘面
bool visited[120][80];//是否访问过
int path[100100];//记录路径最后输出
int counts=0;
int dx[] = {-1,1,0,0};
int dy[] = {0,0,-1,1};
void display()
{
for (int i = 0; i < counts; i+=2)
cout << path[i] << ' ' << path[i + 1]<r||newy<1||newy>c||a[newx][newy] == '*'||visited[newx][newy])
continue;
dfs(newx, newy);
//回溯
path[--counts] = 0;
path[--counts] = 0;
}
}
int main()
{
cin >> r >> c;
for(int i=1;i<=r;i++)
for (int j = 1; j <= c; j++)
{
cin >> a[i][j];
}
dfs(1, 1);
}
这种问题还有很多,比如著名的八皇后,也是这种问题
例(详见洛谷P1596):寻找矩阵中八个方向相连的W块数
如图,共有4块W
我们可以对W的位置且没有被搜索过的位置进行深搜,最终得到的和即为W连通块数
//错误代码
#include
using namespace std;
//只要搜索与水相连的并且标记水即可(把水去掉)
int n, m;
int ans;//水坑数
char a[105][105];
bool visited[105][105];//是否记录过
int dx[] = { -1,-1,-1,0,0,0,1,1,1 };
int dy[] = { -1,0,1,-1,0,1,-1,0,1 };
int dfs(int x,int y)
{
visited[x][y] = 1;
for (int i = 0; i < 9; i++)
{
x += dx[i];
y += dy[i];
if (x<1 || x>n || y<1 || y>m||a[x][y]=='.'||visited[x][y]==1)
continue;
dfs(x, y);
}
return 0;
}
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++)
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
if (a[i][j] == 'W'&&!visited[i][j])
{
ans++;
dfs(i, j);
}
}
cout << ans;
}
咦?为什么不对,这题调试了很久,最终发现,dfs里需要新建变量newx和newy,不能直接对x,y进行操作,这样回溯时,x和y最初的值就再也找不到了
同时我们并不一定需要visited数组判断当前W是否被记录过,在我们对W深搜时,我们可以直接把水'W'改为旱地'.',这样就保证了只有属于不同连通块的W才能被深搜
正确代码如下:
#include
using namespace std;
//只要搜索与水相连的并且标记水即可(把水去掉)
int n, m;
int ans;//水坑数
char a[105][105];
//bool visited[105][105];
int dx[] = { -1,-1,-1,0,0,0,1,1,1 };
int dy[] = { -1,0,1,-1,0,1,-1,0,1 };
int dfs(int x,int y)
{
a[x][y] = '.';//记录过的'W'变成旱地
int newx, newy;
for (int i = 0; i < 9; i++)
{
newx = x + dx[i];
newy = y + dy[i];
if (newx<1 || newx>n || newy<1 || newy>m||a[newx][newy]=='.')
continue;
dfs(newx, newy);
}
return 0;
}
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++)
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
if (a[i][j]=='W')
{
ans++;
dfs(i, j);
}
}
cout << ans;
}
广搜是一层一层向下搜索,所以广搜往往可以让我们找到最先成立的情况,也就是最短路径
例(详见洛谷P1135):有n层楼,每层楼上有一个数字,可以从当前层乘电梯上或下这个数字的层数,求从A层到B层的最短按键次数
比如第一行n层从A到B,第二行每层上的数字,我们这样表示:
结果为3
广搜常常借助队列来完成,将每层数依次放入队列,依次取出,就能保证一层一层,层层递进
代码:
#include
#include
//BFS算法 奇怪的电梯
using namespace std;
bool visited[205];//记录是否已经到过这层,最短路径问题,到走过的楼层无意义
struct node//楼层节点
{
int now;//当前层数
int count;//当前启用电梯次数
};
queueQ;//用来保存当前楼层节点,先进先出
int kk[205];//第i层上的number
int main()
{
int n, a, b;
cin >> n >> a >> b;
for (int i = 1; i <=n; i++)
cin >> kk[i];
Q.push(node{ a, 0 });//入队
while (not Q.empty())
{
node u = Q.front();//提取队首元素
Q.pop();//弹出队首元素
if (u.now == b)//是目标楼层,结束
{
cout << u.count;
return 0;
}
int df = u.now + kk[u.now];//上楼
if (df <= n && not visited[df])//是否能走
{
visited[df] = true;
Q.push(node{ df,u.count + 1 });
}
df = u.now - kk[u.now];//下楼
if (df >= 1 && not visited[df])//是否能走
{
visited[df] = true;
Q.push(node{ df,u.count + 1 });
}
}
cout << "-1";
return 0;
}
又如(详见洛谷P1588):初始位置为x,目标位置为y,每一次可以前进一步、后退一步或者直接走到2*x的位置,求至少需要几步到达目标位置
依然是求最短路径问题
代码:
#include
#include
#include
#include
#define manown 100001
using namespace std;
int dis[manown];//步数&记录
void bfs(int x, int y)
{
memset(dis, -1, sizeof(dis));
queueq;
dis[x] = 0;
q.push(x);
while (!q.empty())
{
int now = q.front();
q.pop();
if (now == y)
{
cout << dis[now] << endl;
}
if (now - 1 > 0 && dis[now - 1] == -1)
{
dis[now - 1] = dis[now] + 1;
q.push(now - 1);
}
if (now + 1 < manown && dis[now + 1] == -1)
{
dis[now + 1] = dis[now] + 1;
q.push(now + 1);
}
if (now * 2 < manown && dis[now * 2] == -1)
{
dis[now * 2] = dis[now] + 1;
q.push(now * 2);
}
}
}
int main()
{
int n;
cin >> n;
while (n--)
{
int x, y;
cin >> x >> y;
bfs(x, y);
}
}
无论是深搜还是广搜,其实都是搜索的一种方式,有时,比如连通块问题,深搜和广搜其实都能胜任,我们应该择优而行之。
最后,在一些题目中(例如背包问题),仅仅使用深搜或者广搜不是最好的办法,甚至都不能AC,这时,我们应该进一步考虑搜索的优化,是不是可以进行一些剪枝?怎样进行一些剪枝?