c - 计算网格上连接组的相邻空点的有效方法

标签 c algorithm x86 simd avx

连通组是指网格上水平或垂直相邻的一组相等值的顶点。

例如,在这个网格上,其中 . 是一个空点,有 4 个相连的组。

X O O .      X       O O
. X O X  ->       X    O  X
X X O X         X X    O  X

您还可以看到每个组都有 1 个连接空点。

我正在尝试计算给定网格和指向组顶点的坐标的这些连接空点的数量。

如果输入是,

. . X X X O O . 
X X . X . X O X 
. X X X X X O X 
X X . O O X . X 

(0, 2)

输出应为 7,因为包含位于 (0, 2)(行、列)处的顶点的大组已连接 7空点。

如果您熟悉围棋(围棋)游戏,那么连接的空点换句话说就是自由。该操作是围棋棋局分析例程的核心,必须快速完成。

以下是我的尝试。在涉及大量分支和递归的许多方面,它的效率非常低。我正在跟踪 4 个可能的方向,并在出现空白时增加计数,并在已完成计数的顶点上做一个标记,以免计数两次。

您能否阐明如何有效地解决这个问题?

一般算法改进和特定于 x86-AVX2 的优化都是受欢迎的。

typedef __attribute__((vector_size(32))) char vec8_c;

enum {H = 4, W = 8};

static int count_();
static int count__();

static int count(vec8_c x, int i, int j) {
    vec8_c m = {0};
    return count_((char *)&x, (char *)&m, i, j, i * W + j);
}

static int count_(char *x, char *m, int i, int j, int idx) {
    m[idx] = 1;
    int r = 0;
    if (j - 1 >= 0) {
        r += count__(x, m, i, j - 1, idx - 1, idx);
    }
    if (j + 1 < W) {
        r += count__(x, m, i, j + 1, idx + 1, idx);
    }
    if (i - 1 >= 0) {
        r += count__(x, m, i - 1, j, idx - W, idx);
    }
    if (i + 1 < H) {
        r += count__(x, m, i + 1, j, idx + W, idx);
    }
    return r;
}

static int count__(char *x, char *m, int i, int j, int idx, int idx_) {
    if (!m[idx]) {
        if (!x[idx]) {
            m[idx] = 1;
            return 1;
        } else if (x[idx] == x[idx_]) {
            return count_(x, m, i, j, idx);
        }
    }
    return 0;
}

Run online .

最佳答案

根据描述,我会将问题转换为交集/并集;

  • 根据连接的组件制作掩码 C
  • 用空像素制作掩模E
  • 通过连接 C 的移位版本来制作更大的掩码 M M = C<<1 | C^^1 | C >> 1 | C^^-1
  • 返回 PopCount(M & E)

这种方法应该很容易矢量化,甚至自动矢量化。

当 C 足够大时,使用 SIMD 寄存器在 16x8 的 block 中工作,其中每个位代表掩码中的一个 bool 值。然后可以将整个 block 向上/向下移动 alignr/左/右 _mm_slli_epi16/_mm_srli_epi16或 AVX2/-512 中的等效项,不幸的是,跨银行转移的成本有点高。

对于特定输入:

. . X X X O O . -> C = 0 0 1 1 1 0 0 0  E = 1 1 0 0 0 0 0 1
X X . X . X O X        1 1 0 1 0 1 0 0      0 0 1 0 1 0 0 0
. X X X X X O X        0 1 1 1 1 1 0 0      1 0 0 0 0 0 0 0
X X . O O X . X        1 1 0 0 0 1 0 0      0 0 1 0 0 0 1 0

那么掩码 M 将是 C 向左、右、上、下移动的并集

M =   0  0  0  1  1  1  0  0  0  0
      0 |1  1  1  1  1  1  0  0| 0  <-- you only need the
      1 |1  1  1  1  1  1  1  0| 0      inner / 'valid' area
      0 |1  1  1  1  1  1  1  0| 0
      1 |1  1  1  1  1  1  1  0| 0
      0  1  1  0  0  0  1  0  0  0

M.*E =    1   1   0   0   0   0   0   0
          0   0   1   0   1   0   0   0
          1   0   0   0   0   0   0   0
          0   0   1   0   0   0   1   0,  sum == 7

关于c - 计算网格上连接组的相邻空点的有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72283931/

相关文章:

assembly - 基本 block 的开始是什么?

virtualization - x86 虚拟化指令集(VT-x、AMD-V)是否有其他用途?

c - 如何编译包含来自另一个库的头文件的 C 程序?

java - 在 Java 中按 "OR"逻辑对谓词进行分区

c++ - 为什么 vsnprintf 是安全的?

algorithm - 与条目数相关的哈希表应该初始化多大?

java - 如何检查一个数 < 1 是否是 2 的幂?

assembly - x86 示例程序中的段错误

c - 访问器函数采用 const arg 但在 C 中返回非 const 指针

c - 如何修复 'invalid argument' 的 ioctl 请求以阻止设备