C++方向数组 + 循环处理邻格

218 阅读5分钟

方向数组 + 循环处理邻格。

  • 这是一个非常重要的算法习惯,它不仅简洁,还能快速拓展到“上下左右”、“八个方向”、“马走日”等等。

所谓方向数组 + 循环处理邻格,是算法竞赛和日常刷题中非常高频、非常高效的一种技巧。


✅ 先理解什么是方向数组

我们来处理八个方向
上、下、左、右、左上、右上、左下、右下。

如果你不用方向数组,就会写成:

cpp
复制编辑
if (a[i-1][j] == ...) // 上  
if (a[i+1][j] == ...) // 下  
if (a[i][j-1] == ...) // 左  
if (a[i][j+1] == ...) // 右  
if (a[i-1][j-1] == ...) // 左上  
if (a[i-1][j+1] == ...) // 右上  
if (a[i+1][j-1] == ...) // 左下  
if (a[i+1][j+1] == ...) // 右下

写得又臭又长。


✅ 用方向数组写法(推荐)

我们定义两个数组:

cpp
复制编辑
int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[8] = {-1,  0,  1,-1, 1,-1, 0, 1};

含义是:

  • (dx[i], dy[i]) 就是第 i 个方向的坐标偏移。
  • 第 0 个方向:(-1, -1) 代表左上角。
  • 第 1 个方向:(-1, 0) 代表上。
  • ...

我们就可以这样循环访问周围的 8 个邻格:

cpp
复制编辑
for (int d = 0; d < 8; ++d) {
    int ni = i + dx[d];
    int nj = j + dy[d];
    // 判断边界
    if (ni >= 0 && ni < 9 && nj >= 0 && nj < 9) {
        // 处理邻格
        next[ni][nj] += ...;
    }
}

✅ 拓展:上下左右(四个方向)

只想处理上下左右?就用:

cpp
复制编辑
int dx[4] = {-1, 1, 0, 0};
int dy[4] = { 0, 0,-1, 1};

✅ 拓展:马走日(象棋/国际象棋)

cpp
复制编辑
int dx[8] = {-2,-2,-1,-1, 1, 1, 2, 2};
int dy[8] = {-1, 1,-2, 2,-2, 2,-1, 1};

✅ 拓展:八方向的 BFS(如迷宫扩散、走棋)

方向数组和 BFS、DFS 结合,可以写得非常整洁、清晰、可拓展。


🧠 总结一句话:

方向数组 + 循环 是“邻接格处理”问题的终极武器,能让你从 if 地狱中解放出来,
不管你是做模拟、递归、搜索、游戏模拟、迷宫走法,只要涉及“周围 4 或 8 个格子”,几乎都能用!

✅ 练习题 1:统计细胞块个数(经典方向数组 + DFS)

📄 题目描述:

给你一个 9×9 的网格(可以用 01 表示),
其中:

  • 1 表示细胞,
  • 0 表示空白。

如果两个 1八个方向上相邻,就认为它们属于同一个细胞块

请你计算出整个网格中,一共有多少个细胞块


🎯 输入格式:

9 行,每行 9 个字符(01

txt
复制编辑
001000000
011000100
000000000
001000000
001100000
000000000
000011100
000011100
000000000

🎯 输出格式:

输出一个整数,表示细胞块的个数。

复制编辑
3

✅ 解题目标:

  • 使用方向数组 dx[8] dy[8]
  • 用 DFS 或 BFS 搜索相邻的 1,标记为“已访问”。
  • 每当你找到一个新的没访问的 1,就计数 +1,启动一次 DFS。

✅ 练习题 2:水池边界判定(只用上下左右方向)

📄 题目描述:

给你一个 5×5 的地图(输入为字符):

  • . 表示陆地
  • ~ 表示水

一个水池由一片 ~ 相连的区域组成(只看上下左右方向相邻)。

请你输出地图上是否存在**“被陆地完全包围”的水池**(也就是说,水池没有连接地图边界)。


示例输入:

txt
复制编辑
.....
.~~~.
.~~~.
.~~~.
.....

输出:

yaml
复制编辑
Yes

示例输入:

txt
复制编辑
~~..~
~~..~
.....
.....
.....

输出:

yaml
复制编辑
No

✅ 解题目标:

  • 只用 dx[4] dy[4]
  • 用 DFS 从每个 ~ 开始搜索。
  • 判断这一片水池有没有连到边界。
  • 如果有一个水池完全没碰边界,那就输出 Yes,否则 No

💡 提示:

八方向:

cpp
复制编辑
int dx[8] = {-1,-1,-1,0,0,1,1,1};
int dy[8] = {-1,0,1,-1,1,-1,0,1};

四方向:

cpp
复制编辑
int dx[4] = {-1,1,0,0};
int dy[4] = {0,0,-1,1};


✅ 题目再复述一下

地图是一个 5×5 的字符网格:

  • ~ 表示水。
  • . 表示陆地。
  • 水池:一块连在一起的 ~
  • 只看上下左右是否相邻。

你要判断是否存在一个完整被陆地包围的水池(不连接地图边界)。


✅ 示例一

txt
复制编辑
.....
.~~~.
.~~~.
.~~~.
.....

👉 水池没有接触边界 → 输出:Yes


✅ 示例二

txt
复制编辑
~~..~
~~..~
.....
.....
.....

👉 水池从左上角直接连到边界 → 输出:No


✅ 解题思路(DFS版本):

  1. 建立地图 char grid[5][5];
  2. 建立 visited[5][5] 数组。
  3. 对每个没访问过的 ~ 发起 DFS,构成一个水池。
  4. 在 DFS 过程中记录是否碰到了边界。
  5. 如果某个水池完全没碰边界,就说明它是被包围的,输出 Yes
  6. 否则,最后输出 No

✅ 方向数组(上下左右):

cpp
复制编辑
int dx[4] = {-1, 1, 0, 0};
int dy[4] = { 0, 0,-1, 1};

✅ 模板代码结构(你可以自己写,也可以填空):

cpp
复制编辑
#include <iostream>
using namespace std;

char grid[5][5];
bool visited[5][5];
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
bool touchBorder; // 这个变量表示当前水池是否接触到边界

void dfs(int x, int y) {
    visited[x][y] = true;

    // 判断是否在边界上
    if (x == 0 || x == 4 || y == 0 || y == 4)
        touchBorder = true;

    for (int d = 0; d < 4; d++) {
        int nx = x + dx[d];
        int ny = y + dy[d];
        if (nx >= 0 && nx < 5 && ny >= 0 && ny < 5) {
            if (!visited[nx][ny] && grid[nx][ny] == '~') {
                dfs(nx, ny);
            }
        }
    }
}

int main() {
    // 读入地图
    for (int i = 0; i < 5; i++)
        cin >> grid[i];

    // 枚举所有格子
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            if (!visited[i][j] && grid[i][j] == '~') {
                touchBorder = false;
                dfs(i, j);
                if (!touchBorder) {
                    cout << "Yes" << endl;
                    return 0;
                }
            }
        }
    }

    cout << "No" << endl;
    return 0;
}

✅ 推荐你现在去做的步骤:

  • 自己独立敲一遍。

  • 用两组数据测试:

    • Yes:中间的水池。
    • No:角落的水池。

如果你愿意挑战一下,还可以试试改成 BFS 版本(我也可以提供指导)。