从DFS到BFS:一匹马引发的算法惨案

"こんにちは!" 我是Zac!今天要给大家讲一个关于一匹不听话的马,和两个程序员相爱相杀的故事。准备好了吗?系好安全带,我们要发车了!

先上题目吧:timu传送门

题目

题目描述

有一个 n×m 的棋盘,在某个点 (x,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。

输入格式

输入只有一行四个整数,分别为 n,m,x,y。

输出格式

一个 n×m 的矩阵,代表马到达某个点最少要走几步(不能到达则输出 −1)。

输入输出样例

输入#1

3 3 1 1

输出#1

0 3 2
3 -1 1
2 1 4

说明/提示

数据规模与约定

对于全部的测试点,保证 1≤x≤n≤400,1≤y≤m≤400。

2022 年 8 月之后,本题去除了对输出保留场宽的要求。为了与之兼容,本题的输出以空格或者合理的场宽分割每个整数都将判作正确。

 

第一幕:天真程序员的幻想

"这不就是个简单的搜索题嘛!" 我对着屏幕傻笑,仿佛已经看到了AC的曙光。毕竟我可是刚学会DFS的"大佬"啊!(后来证明是"大老",又大又老又菜)

 DFS思路:让马儿自由飞翔


DFS的思路就像放养一匹野马:
1. 从起点出发,随便选一个方向跳(日字有8种跳法呢)
2. 跳到新位置后继续随便跳
3. 记录到达每个位置的最小步数
4. 如果发现更短的路径就更新
5. 直到...呃...直到所有可能性都试完?

// 此处的DFS代码,实在太丢人了
#include <iostream>
#include <climits>
using namespace std;
const int N = 405;
const int dir[8][2] = {
    {-2, -1}, {-2, 1},
    {-1, -2}, {-1, 2},
    {1, -2}, {1, 2},
    {2, -1}, {2, 1}
};
int n, m, x, y;
int dis[N][N];
void dfs(int i, int j, int s) {
    if (i < 0 || i >= n || j < 0 || j >= m) return ;
    if (s >= dis[i][j] && dis[i][j] != -1) return ;
    dis[i][j] = s;
    for (int k = 0; k < 8; k++) {
        int ni = i + dir[k][0];
        int nj = j + dir[k][1];
        dfs(ni, nj, s + 1);
    }
}
int main() {
    cin >> n >> m >> x >> y;
    x--;
    y--;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            dis[i][j] = -1;
        }
    }
    dfs(x, y, 0);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cout << dis[i][j];
            if (j < m - 1) cout << " ";
        }
        cout << endl;
    }
    return 0;
}
// 就像我的初恋一样不堪回首(我有初恋吗?)

第二幕:现实的重锤

当我看到400x400的棋盘时,我的DFS就像:
- 一匹喝醉的马
- 在迷宫里瞎转悠
- 时不时撞墙
- 还总走回头路

DFS为什么不行?


1. 递归深度太大,容易爆栈(马儿累死了)
2. 会重复访问同一个位置无数次(马儿迷路了)
3. 无法保证第一次访问就是最短路径(马儿绕远路)
4. 时间复杂度爆炸(马儿跑不动了)

评测机内心OS:"这孩子怕不是个傻子吧?"

 第三幕:救世主BFS登场

就在我准备放弃治疗的时候,BFS像超级英雄一样出现了!它带着队列这个秘密武器,准备教DFS怎么做人。

BFS思路:训练有素的马术表演


BFS的工作方式就像专业的马术表演:
1. 让马儿们排好队(队列初始化)
2. 从起点开始,记录步数为0(起点不需要跳)
3. 每次取出队首的马儿,让它尝试所有8种跳法
4. 对于每个新位置:
   - 如果是第一次到达,记录步数并入队
   - 否则跳过(因为BFS保证第一次到达就是最短路径)
5. 直到队列为空(所有可达位置都访问过)
 

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
// 此处是优雅的BFS代码
const int N = 405;
const int dr[8] = {-2, -2, -1, -1,  1, 1,  2, 2};
const int dc[8] = {-1,  1, -2,  2, -2, 2, -1, 1};
int n, m, x, y;
int d[N][N];
queue<pair<int, int>> q;
void bfs() {
    while (!q.empty()) {
        int r = q.front().first;
        int c = q.front().second;
        q.pop();
        for (int k = 0; k < 8; k++) {
            int nr = r + dr[k];
            int nc = c + dc[k];
            if (nr >= 0 && nr < n && nc >= 0 && nc < m && d[nr][nc] == -1) {
                d[nr][nc] = d[r][c] + 1;
                q.push({nr, nc});
            }
        }
    }
}
int main() {
    cin >> n >> m >> x >> y;
    x--;
    y--;
    memset(d, -1, sizeof(d));
    d[x][y] = 0;
    q.push({x, y});
    bfs();
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cout << d[i][j];
            if (j < m - 1) cout << " ";
        }
        cout << endl;
    }
    return 0;
}

// 就像我的现任女友一样完美(我有女友吗???)

 为什么BFS这么秀?

1. **队列管理**:让马儿们乖乖排队,不插队不抢跑
2. **层次遍历**:保证先处理距离近的点,再处理远的点
3. **最短路径**:第一次到达就是最短路径,不用绕弯子
4. **效率保障**:每个点只处理一次,时间复杂度O(nm)

血泪教训总结

1. DFS适用场景:
   - 需要探索所有可能路径的问题
   - 拓扑排序、连通分量等问题
   - 就像青春期的恋爱:冲动、盲目、容易陷入太深

2. BFS适用场景:
   - 无权图的最短路径问题
   - 层次遍历问题
   - 就像成熟后的婚姻:稳重、有序、一步一个脚印

3. 评测机真相:
   - 它就像严格的丈母娘
   - 永远知道你哪里不行
   - 但也会在你做对时给你满分

最后送给大家一句忠告:看到"最短路径"四个字,请直接选择BFS,除非你想像我一样体验社会性死亡!

"Au revoir!" 我是Zac,我们下次再见!(如果我还活着从这道题里爬出来的话)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值