【问题标题】:3d array traversal originating from center源自中心的 3d 数组遍历
【发布时间】:2016-06-10 12:54:23
【问题描述】:

我正在尝试为具有统一维度n 的 3d 数组找到遍历顺序。因此,遍历顺序应按其到立方体中心的距离升序排序(具有相同索引的单元格的顺序是任意的)。 二维数组示例:

7 4 8
3 0 1
6 2 5

这基本上是Manhattan metric中的距离:

2 1 2
1 0 1
2 1 2

作为相对于原点的相对坐标遍历:

[ 0, 0]
[ 1, 0]
[ 0,-1]
[-1, 0]
[ 0, 1]
[ 1,-1]
[-1,-1]
[-1, 1]
[ 1, 1]

我知道一些解决这个问题的方法,例如预先计算所有索引并根据它们到原点的距离对它们进行排序。但是,由于此算法旨在在 GPU 上执行,因此存在一些限制:

  1. 无递归(我知道将递归解析为 一种迭代算法 - 然而,维护堆栈不是 根据我的经验,合适的解决方案)
  2. 无离线计算(= 在 CPU 上计算并将结果传输到 GPU)。解决方案需要像 可能

在搜索解决方案时,我偶然发现了这个问题,这正是我倾向于解决的问题,但接受的答案虽然包含不符合指定要求的树结构:3D Array Traversal in Different Order

我还想到了一种使用球坐标创建索引的方法,不幸的是,这并不能产生正确的顺序。为 3d 数组生成给定遍历顺序的合适算法是什么?

编辑:Stormwind 为给定的问题提供了一个很好的替代描述:“[问题] 实际上是将寻址从一个空间转换为另一个空间。一维和二维之间的转换很简单,例如 1, 2,3,... 到 (1,1),(1,2)(2,1)... 但这更像是从升序一维(或至少平方)转换为“升序八面体”分层”空间,当“升序”表示“最内层优先”时,除了每个层表面上现有的(尽管是任意的)递增顺序。”

【问题讨论】:

  • 遍历顺序是否只由到中心的距离决定?因为那么在您的 2D 示例中,任何以 0 为中心的排序都是正确的。或者您是否专门寻找“螺旋”类型的遍历顺序,您总是移动到相邻的单元格?
  • N 总是奇数还是偶数?距离是欧几里得还是曼哈顿?
  • @Spektre N 可以是两者。距离是曼哈顿。
  • @Dual:如果距离不是标准的欧几里得距离,这很重要。您应该更新问题。
  • 你说的问题必须在GPU上解决,你是说使用着色器和浮点曲面吗? GPGPU?

标签: arrays algorithm 3d gpu


【解决方案1】:

经过一段时间的思考,我想出了一个想法,将 3d 数组表示为具有方向的节点序列:+i、-i、+j、-j、+k、@987654330 @。

方法

对于二维数组,只有三个规则就足够了:

  1. 每个节点上的每次迭代都会沿其轴在其方向上移动它,即节点 +j 将增加第二个索引,节点 -i 将减少第一个索引。
  2. 有两种节点:Main 和Secondary。主轴有一个索引0。 Main i 和 j 轴节点(我将它们称为 I 和 J)的每次迭代都会产生 Secondary 节点顺时针旋转 90 度:
    • +I -> -j
    • -J -> -i
    • -I -> +j
    • +J -> +i
  3. 每个节点都有生命周期,每次迭代都会递减。对于 n 的奇数值,节点的生命周期等于 (n-1)/2(对于偶数值,请参见下面的代码)。生命周期为 0 后,应删除该节点。

要启用第三维,应应用另一个规则:

  1. 沿k 轴(此处为深度)方向的第三种节点在每次迭代中产生一组I 和J 轴:
    • +K -> +I, -J, -I, +J
    • -K -> +I, -J, -I, +J

它的外观如下:

使用这种方法,元素将按 Manhattan distance 自动排序,就像在 Arturo Menchaca 解决方案中一样。

实施

这是执行我所描述的操作的 python 代码。有很大的改进空间,这只是一个概念证明。它没有排序,没有递归,我没有看到任何离线计算。 它还包含一些测试。 Run

NO = ( 0, 0, 0, 2, 0)
Pi = (+1, 0, 0, 0, 0)
PI = (+1, 0, 0, 0, 1)
Pj = ( 0,+1, 0, 0, 0)
PJ = ( 0,+1, 0, 0, 1)
PK = ( 0, 0,+1, 0, 2)
Mi = (-1, 0, 0, 1, 0)
MI = (-1, 0, 0, 1, 1)
Mj = ( 0,-1, 0, 1, 0)
MJ = ( 0,-1, 0, 1, 1)
MK = ( 0, 0,-1, 1, 2)
#      i  j  k  ^  ^
#               |  Id for comparison
#               Lifetime index

PRODUCE = {
    PI: [ Mj ], # +I -> -j
    MJ: [ Mi ], # -J -> -i
    MI: [ Pj ], # -I -> +j
    PJ: [ Pi ], # +J -> +i
    NO: [ NO ],
    Pi: [ NO ],
    Pj: [ NO ],
    Mi: [ NO ],
    Mj: [ NO ],
    PK: [ PI, MI, PJ, MJ ], # +K -> +I, -J, -I, +J
    MK: [ PI, MI, PJ, MJ ], # -K -> +I, -J, -I, +J
}


class Index:
    LastDistance = 0
    NumberOfVisits = 0
    MinIndex = 0
    MaxIndex = 0
    def __init__(self, i, j, k, lifetime, direction):
        self.globalLifetime = lifetime
        self.direction = direction

        # Assign parent's position
        self.i = i
        self.j = j
        self.k = k

        # Step away from parent
        self.lifetime = lifetime[direction[3]]
        self.step()

    def isLive(self):
        return self.lifetime > 0

    def visit(self):
        Index.NumberOfVisits += 1
        distance = self.distance()
        if distance < Index.LastDistance:
           raise NameError("Order is not preserved")
        Index.LastDistance = distance
        Index.MinIndex = min(self.i, Index.MinIndex)
        Index.MinIndex = min(self.j, Index.MinIndex)
        Index.MinIndex = min(self.k, Index.MinIndex)
        Index.MaxIndex = max(self.i, Index.MaxIndex)
        Index.MaxIndex = max(self.j, Index.MaxIndex)
        Index.MaxIndex = max(self.k, Index.MaxIndex)
        print("[{}, {}, {}]".format(self.i, self.j, self.k))

    def step(self):
        # Move in your direction
        self.i += self.direction[0]
        self.j += self.direction[1]
        self.k += self.direction[2]

    def iterate(self):
        self.lifetime -= 1

    def produce(self, result):
        for direction in PRODUCE[self.direction]:
            self.create(direction, result)

    def create(self, direction, result):
        index = Index(self.i, self.j, self.k, self.globalLifetime, direction)
        if index.isLive():
            result.append(index)

    def distance(self):
        # Manhattan Distance
        return abs(self.i) + abs(self.j) + abs(self.k)

def Traverse(N):
    TotalNumber = N*N*N
    halfA = halfB = (N-1)/2
    if N % 2 == 0:
        halfA = N/2
        halfB = N/2-1

    MinIndex = -min(halfB, halfA)
    MaxIndex = max(halfB, halfA)

    lifetime = (halfA, halfB, 0)

    SecondaryNodes = []
    MainNodes = []
    KNodes = []

    # visit center
    center = Index(0, 0, 0, lifetime, NO)
    center.visit()

    center.create(PI, MainNodes)
    center.create(MI, MainNodes)
    center.create(PJ, MainNodes)
    center.create(MJ, MainNodes)
    center.create(PK, KNodes)
    center.create(MK, KNodes)

    while len(SecondaryNodes) + len(MainNodes) + len(KNodes) > 0:

        # First - visit all side nodes
        temp = []
        for m in SecondaryNodes:
            m.visit()
            m.step()
            m.iterate()
            # Save node only if it is alive
            if m.isLive():
                temp.append(m)

        SecondaryNodes = temp

        # Second - visit all main nodes as they may produce secondary nodes
        temp = []
        for m in MainNodes:
            m.visit() # 1 - Visit
            m.produce(SecondaryNodes) # 2 - Produce second
            m.step() # 3 - Step 
            m.iterate() # 4 - Lose a life
            if m.isLive():
                temp.append(m)

        MainNodes = temp

        # Third - visit all K nodes as they may produce main nodes
        temp = []
        for m in KNodes:
            m.visit()
            m.produce(MainNodes)
            m.step()
            m.iterate()
            if m.isLive():
                temp.append(m)

        KNodes = temp
    if TotalNumber != Index.NumberOfVisits:
        raise NameError("Number of visited elements does not match {}/{}".format(Index.NumberOfVisits, TotalNumber))
    if MinIndex != Index.MinIndex:
        raise NameError("Minimal index is out of bounds {}/{}".format(Index.MinIndex, MinIndex))
    if MaxIndex != Index.MaxIndex:
        raise NameError("Maximal index is out of bounds {}/{}".format(Index.MaxIndex, MaxIndex))

Traverse(6)

实现简化

帮助类来存储索引:

class Index:
    def __init__(self, i, j, k, lifetime):
        self.i = i
        self.j = j
        self.k = k
        self.lifetime = lifetime

    def visit(self):
        print("[{}, {}, {}]".format(self.i, self.j, self.k))

在正确方向上迭代Main 节点的函数集:

def StepMainPlusI(mainPlusI, minusJ, lifetime):
    result = []
    for m in mainPlusI:
        if lifetime > 0:
            minusJ.append(Index(m.i, m.j-1, m.k, lifetime))
        m.lifetime -= 1
        m.i += 1
        if m.lifetime > 0:
            result.append(m)
    return result

def StepMainMinusJ(mainMinusJ, minusI, lifetime):
    result = []
    for m in mainMinusJ:
        if lifetime > 0:
            minusI.append(Index(m.i-1, m.j, m.k, lifetime))
        m.lifetime -= 1
        m.j -= 1
        if m.lifetime > 0:
            result.append(m)
    return result

def StepMainMinusI(mainMinusI, plusJ, lifetime):
    result = []
    for m in mainMinusI:
        if lifetime > 0:
            plusJ.append(Index(m.i, m.j+1, m.k, lifetime))
        m.lifetime -= 1
        m.i -= 1
        if m.lifetime > 0:
            result.append(m)
    return result

def StepMainPlusJ(mainPlusJ, plusI, lifetime):
    result = []
    for m in mainPlusJ:
        if lifetime > 0:
            plusI.append(Index(m.i+1, m.j, m.k, lifetime))
        m.lifetime -= 1
        m.j += 1
        if m.lifetime > 0:
            result.append(m)
    return result

迭代第三维K节点的函数集:

def StepMainPlusK(mainPlusK, mainPlusI, mainMinusI, mainPlusJ, mainMinusJ, lifetimeA, lifetimeB):
    result = []
    for m in mainPlusK:
        if lifetimeA > 0:
            mainPlusI.append(Index(+1, 0, m.k, lifetimeA))
            mainPlusJ.append(Index(0, +1, m.k, lifetimeA))
        if lifetimeB > 0:
            mainMinusI.append(Index(-1, 0, m.k, lifetimeB))
            mainMinusJ.append(Index(0, -1, m.k, lifetimeB))
        m.lifetime -= 1
        m.k += 1
        if m.lifetime > 0:
            result.append(m)
    return result

def StepMainMinusK(mainMinusK, mainPlusI, mainMinusI, mainPlusJ, mainMinusJ, lifetimeA, lifetimeB):
    result = []
    for m in mainMinusK:
        if lifetimeA > 0:
            mainPlusI.append(Index(+1, 0, m.k, lifetimeA))
            mainPlusJ.append(Index(0, +1, m.k, lifetimeA))
        if lifetimeB > 0:
            mainMinusI.append(Index(-1, 0, m.k, lifetimeB))
            mainMinusJ.append(Index(0, -1, m.k, lifetimeB))
        m.lifetime -= 1
        m.k -= 1
        if m.lifetime > 0:
            result.append(m)
    return result

当n 是奇数并且一半可以小于另一个时,这两个函数有两个不同的生命周期参数。我已将它们按符号划分 - 负向将具有下半部分的索引。

迭代Secondary节点的函数集:

def StepPlusI(plusI):
    result = []
    for m in plusI:
        m.i += 1
        m.lifetime -= 1
        if m.lifetime > 0:
            result.append(m)
    return result

def StepMinusI(minusI):
    result = []
    for m in minusI:
        m.i -= 1
        m.lifetime -= 1
        if m.lifetime > 0:
            result.append(m)
    return result

def StepPlusJ(plusJ):
    result = []
    for m in plusJ:
        m.j += 1
        m.lifetime -= 1
        if m.lifetime > 0:
            result.append(m)
    return result

def StepMinusJ(minusJ):
    result = []
    for m in minusJ:
        m.j -= 1
        m.lifetime -= 1
        if m.lifetime > 0:
            result.append(m)
    return result

主要功能:

def Traverse(N):
    halfA = halfB = (N-1)/2
    if N % 2 == 0: # size is even
        halfA = N/2
        halfB = N/2-1

    # visit center
    Index(0,0,0,0).visit()

    # Secondary nodes
    PlusI  = []
    MinusI = []
    PlusJ  = []
    MinusJ = []

    # Main nodes
    MainPlusI  = []
    MainMinusI = []
    MainPlusJ  = []
    MainMinusJ = []
    MainPlusK  = []
    MainMinusK = []

    # Add Main nodes
    if halfA > 0:
        MainPlusI.append(  Index(+1, 0, 0, halfA) )
        MainPlusJ.append(  Index(0, +1, 0, halfA) )
        MainPlusK.append(  Index(0, 0, +1, halfA) )

    if halfB > 0:
        MainMinusI.append( Index(-1, 0, 0, halfB) )
        MainMinusJ.append( Index(0, -1, 0, halfB) )
        MainMinusK.append( Index(0, 0, -1, halfB) )

    # Finish condition flag
    visited = True
    while visited:
        visited = False

        # visit all Main nodes
        for m in MainPlusI:
            m.visit()
            visited = True
        for m in MainMinusI:
            m.visit()
            visited = True
        for m in MainPlusJ:
            m.visit()
            visited = True
        for m in MainMinusJ:
            m.visit()
            visited = True
        for m in MainPlusK:
            m.visit()
            visited = True
        for m in MainMinusK:
            m.visit()
            visited = True

        # Visit all Secondary nodes
        for m in PlusI:
            m.visit()
            visited = True
        for m in MinusI:
            m.visit()
            visited = True
        for m in PlusJ:
            m.visit()
            visited = True
        for m in MinusJ:
            m.visit()
            visited = True

        # Iterate Secondary nodes first
        PlusI = StepPlusI(PlusI)
        MinusI = StepMinusI(MinusI)
        PlusJ = StepPlusJ(PlusJ)
        MinusJ = StepMinusJ(MinusJ)

        # Iterate all Main nodes as they might generate Secondary nodes
        MainPlusI = StepMainPlusI(MainPlusI, MinusJ, halfB)
        MainMinusJ = StepMainMinusJ(MainMinusJ, MinusI, halfB)
        MainMinusI = StepMainMinusI(MainMinusI, PlusJ, halfA)
        MainPlusJ = StepMainPlusJ(MainPlusJ, PlusI, halfA)

        # Iterate K nodes last as they might produce Main nodes
        MainPlusK = StepMainPlusK(MainPlusK, MainPlusI, MainMinusI, MainPlusJ, MainMinusJ, halfA, halfB)
        MainMinusK = StepMainMinusK(MainMinusK, MainPlusI, MainMinusI, MainPlusJ, MainMinusJ, halfA, halfB)

还有活生生的例子Code

【讨论】:

    【解决方案2】:

    八分圆对称性

    立方矩阵中距中心一定曼哈顿距离的单元形成一个八面体,该八面体关于穿过立方体中心的 xy、xz 和 yz 平面对称。

    这意味着您只需要在立方体的第一个octant 中找到形成八面体一个面的单元格,然后镜像它们以获取其他 7 个八分圆中的单元格。所以问题被简化为对角线穿过立方体的第一个八分圆(它本身就是一个立方体),从中心(距离 0)到角单元(最大距离 = 3 × n/2)。

    寻找坐标的算法

    在第一个八分圆中找到与 (0,0,0) 单元相距一定曼哈顿距离的单元(即形成八面体一个面的单元,垂直于立方体的对角线),意味着查找坐标 (x,y,z) 总和为该距离的单元格。因此,在 5x5x5 八分圆的示例中,距离为 3 的单元格是具有坐标的单元格:

    (3,0,0) (2,1,0) (1,2,0) (0,3,0)
    (2,0,1) (1,1,1) (0,2,1)
    (1,0,2) (0,1,2)
    (0,0,3)

    您会注意到距离分区的相似性(实际上,它是所谓的weak composition,有界长度为 3)。

    使用三个嵌套循环可以轻松找到这些组合;唯一的复杂性是每个维度的距离限制为 n/2,因此您必须跳过不存在 z 值的 x 和/或 y 值,以便 x、y 和 z 总和为距离;这就是 JavaScript 代码示例中的 min() 和 max() 以及 C 代码示例中的 xmin、xmax、ymin 和 ymax 变量的用途。

    均匀大小的立方体中的单元格的镜像很简单;在奇数大小的立方体中,单元格不会在其坐标为零的维度上镜像(即当单元格位于对称平面时)。这就是代码示例中检查 x、y 或 z 是否为零的目的。

    并行编程

    我对 GPU 编程了解不多,但我认为您可以完全并行化算法。对于外循环的每次迭代(即对于每个距离),一旦计算出 x 的最小值和最大值,就可以并行运行具有不同 x 值的迭代。然后对于 x 的每个值,一旦计算了 y 的最小值和最大值,就可以并行运行具有不同 y 值的迭代。最后,对于 (x,y,z) 的每个坐标集,可以并行运行对其他八分圆的镜像。

    代码示例 1 (JavaScript)

    (运行代码 sn-p 以查看 9x9x9 矩阵的由内向外遍历,如图所示。)

    function insideOut(n) {
        var half = Math.ceil(n / 2) - 1;
        for (var d = 0; d <= 3 * half; d++) {
            for (var x = Math.max(0, d - 2 * half); x <= Math.min(half, d); x++) {
                for (var y = Math.max(0, d - x - half); y <= Math.min(half, d - x); y++) {
                    document.write("<br>distance " + d + " (&plusmn;" + x + ",&plusmn;" + y + ",&plusmn;" + (d - x - y) + ") &rarr; ");
                    n % 2 ? mirrorOdd(x, y, d - x - y) : mirrorEven(x, y, d - x - y);
                }
            }
        }
        function mirrorEven(x, y, z) {
            for (var i = 1; i >= 0; --i, x *= -1) {
                for (var j = 1; j >= 0; --j, y *= -1) {
                    for (var k = 1; k >= 0; --k, z *= -1) {
                        visit(half + x + i, half + y + j, half + z + k);
                    }
                }
            }
        }
        function mirrorOdd(x, y, z) {
            for (var i = 0; i < (x ? 2 : 1); ++i, x *= -1) {
                for (var j = 0; j < (y ? 2 : 1); ++j, y *= -1) {
                    for (var k = 0; k < (z ? 2 : 1); ++k, z *= -1) {
                        visit(half + x, half + y, half + z);
                    }
                }
            }
        }
        function visit(x, y, z) {
            document.write("(" + x + "," + y + "," + z + ") " );
        }
    }
    insideOut(9);

    代码示例 2 (C)

    为简单起见,可以展开镜像功能。事实上,整个算法不外乎3个嵌套循环和简单的整数计算。

    void mirrorEven(unsigned int x, unsigned int y, unsigned int z, unsigned int h) {
        visit(h+x+1, h+y+1, h+z+1);
        visit(h+x+1, h+y+1, h-z);
        visit(h+x+1, h-y,   h+z+1);
        visit(h+x+1, h-y,   h-z);
        visit(h-x,   h+y+1, h+z+1);
        visit(h-x,   h+y+1, h-z);
        visit(h-x,   h-y,   h+z+1);
        visit(h-x,   h-y,   h-z);
    }
    void mirrorOdd(unsigned int x, unsigned int y, unsigned int z, unsigned int h) {
                         visit(h+x, h+y, h+z);
        if (          z) visit(h+x, h+y, h-z);
        if (     y     ) visit(h+x, h-y, h+z);
        if (     y && z) visit(h+x, h-y, h-z);
        if (x          ) visit(h-x, h+y, h+z);
        if (x      && z) visit(h-x, h+y, h-z);
        if (x && y     ) visit(h-x, h-y, h+z);
        if (x && y && z) visit(h-x, h-y, h-z);
    }
    void insideOut(unsigned int n) {
        unsigned int d, x, xmin, xmax, y, ymin, ymax, half = (n-1)/2;
        for (d = 0; d <= 3*half; d++) {
            xmin = d < 2*half ? 0 : d-2*half;
            xmax = d < half ? d : half;
            for (x = xmin; x <= xmax; x++) {
                ymin = d < x+half ? 0 : d-x-half;
                ymax = d > x+half ? half : d-x;
                for (y = ymin; y <= ymax; y++) {
                    if (n%2) mirrorOdd(x, y, d-x-y, half);
                    else mirrorEven(x, y, d-x-y, half);
                }
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      [我在解决方案中使用Manhattan distance]

      为简单起见,让我们开始假设奇数维的 3D 数组 ([2N+1, 2N+1, 2N+1])

      使用 曼哈顿距离 中心 ([0,0,0]) 和点之间的最大距离是 3N ([N,N,N], [N,N,-N], ...)

      所以,基本上这个想法是找到一种方法来生成所有具有特定距离的坐标。然后从距离0 到3N 生成这些坐标。

      要生成坐标[X,Y,Z] 到某个值K 的中心距离,我们需要的是-N 和N 之间的所有数字X、Y、Z,这样ABS(X) + ABS(Y) + ABS(Z) == K .可以这样做:

      FUNC COORDS_AT_DIST(K)
          FOR X = -MIN(N, K) TO MIN(N, K)
              FOR Y = -MIN(N, K - ABS(X)) TO MIN(N, K - ABS(X))
                  LET Z = K - ABS(X) - ABS(Y)
                  IF Z <= N
                      VISIT(X, Y, Z)
                      IF Z != 0
                          VISIT(X, Y, -Z)
      

      然后,像这样使用这个函数:

      FOR K = 0 TO 3N
          COORDS_AT_DIST(K)
      

      这段代码访问所有坐标值在[-N,-N,-N]和[N,N,N]之间的坐标,按照到[0,0,0]的距离排序。

      现在,为了也处理偶数维度,我们需要进行一些额外检查,因为维度 L 的坐标值介于 [-(L/2-1),-(L/2-1),-(L/2-1)] 和 [L/2,L/2,L/2] 之间。

      类似这样的:

      FUNC VISIT_COORDS_FOR_DIM(L)
          LET N = L/2               //Integer division
          FOR K = 0 TO 3N
              FOR X = -MIN(N - REM(L+1, 2), K) TO MIN(N, K)
                  FOR Y = -MIN(N - REM(L+1, 2), K - ABS(X)) TO MIN(N, K - ABS(X))
                      LET Z = K - ABS(X) - ABS(Y)
                      IF Z <= N
                          VISIT(X, Y, Z)
                          IF Z != 0 && (REM(L, 2) != 0 || Z < N)
                              VISIT(X, Y, -Z)
      

      为了清楚起见:

      MIN(X, Y): Minimum value between X and Y
      ABS(X): Absolute value of X
      REM(X, Y): Remainder after division of X by Y
      
      VISIT(X, Y, Z): Visit the generated coordinate (X, Y, Z)
      

      将VISIT_COORDS_FOR_DIM 函数与L=3 一起使用,您会得到:

       1. [0, 0, 0]       DISTANCE: 0
       2. [-1, 0, 0]      DISTANCE: 1
       3. [0, -1, 0]      DISTANCE: 1
       4. [0, 0, -1]      DISTANCE: 1
       5. [0, 0, 1]       DISTANCE: 1
       6. [0, 1, 0]       DISTANCE: 1
       7. [1, 0, 0]       DISTANCE: 1
       8. [-1, -1, 0]     DISTANCE: 2
       9. [-1, 0, -1]     DISTANCE: 2
      10. [-1, 0, 1]      DISTANCE: 2
      11. [-1, 1, 0]      DISTANCE: 2
      12. [0, -1, -1]     DISTANCE: 2
      13. [0, -1, 1]      DISTANCE: 2
      14. [0, 1, -1]      DISTANCE: 2
      15. [0, 1, 1]       DISTANCE: 2
      16. [1, -1, 0]      DISTANCE: 2
      17. [1, 0, -1]      DISTANCE: 2
      18. [1, 0, 1]       DISTANCE: 2
      19. [1, 1, 0]       DISTANCE: 2
      20. [-1, -1, -1]    DISTANCE: 3
      21. [-1, -1, 1]     DISTANCE: 3
      22. [-1, 1, -1]     DISTANCE: 3
      23. [-1, 1, 1]      DISTANCE: 3
      24. [1, -1, -1]     DISTANCE: 3
      25. [1, -1, 1]      DISTANCE: 3
      26. [1, 1, -1]      DISTANCE: 3
      27. [1, 1, 1]       DISTANCE: 3
      

      对于L=4:

       1. [0, 0, 0]      DISTANCE: 0                    33. [1, -1, -1]    DISTANCE: 3
       2. [-1, 0, 0]     DISTANCE: 1                    34. [1, -1, 1]     DISTANCE: 3
       3. [0, -1, 0]     DISTANCE: 1                    35. [1, 0, 2]      DISTANCE: 3
       4. [0, 0, -1]     DISTANCE: 1                    36. [1, 1, -1]     DISTANCE: 3
       5. [0, 0, 1]      DISTANCE: 1                    37. [1, 1, 1]      DISTANCE: 3
       6. [0, 1, 0]      DISTANCE: 1                    38. [1, 2, 0]      DISTANCE: 3
       7. [1, 0, 0]      DISTANCE: 1                    39. [2, -1, 0]     DISTANCE: 3
       8. [-1, -1, 0]    DISTANCE: 2                    40. [2, 0, -1]     DISTANCE: 3
       9. [-1, 0, -1]    DISTANCE: 2                    41. [2, 0, 1]      DISTANCE: 3
      10. [-1, 0, 1]     DISTANCE: 2                    42. [2, 1, 0]      DISTANCE: 3
      11. [-1, 1, 0]     DISTANCE: 2                    43. [-1, -1, 2]    DISTANCE: 4
      12. [0, -1, -1]    DISTANCE: 2                    44. [-1, 1, 2]     DISTANCE: 4
      13. [0, -1, 1]     DISTANCE: 2                    45. [-1, 2, -1]    DISTANCE: 4
      14. [0, 0, 2]      DISTANCE: 2                    46. [-1, 2, 1]     DISTANCE: 4
      15. [0, 1, -1]     DISTANCE: 2                    47. [0, 2, 2]      DISTANCE: 4
      16. [0, 1, 1]      DISTANCE: 2                    48. [1, -1, 2]     DISTANCE: 4
      17. [0, 2, 0]      DISTANCE: 2                    49. [1, 1, 2]      DISTANCE: 4
      18. [1, -1, 0]     DISTANCE: 2                    50. [1, 2, -1]     DISTANCE: 4
      19. [1, 0, -1]     DISTANCE: 2                    51. [1, 2, 1]      DISTANCE: 4
      20. [1, 0, 1]      DISTANCE: 2                    52. [2, -1, -1]    DISTANCE: 4
      21. [1, 1, 0]      DISTANCE: 2                    53. [2, -1, 1]     DISTANCE: 4
      23. [2, 0, 0]      DISTANCE: 2                    54. [2, 0, 2]      DISTANCE: 4
      23. [-1, -1, -1]   DISTANCE: 3                    55. [2, 1, -1]     DISTANCE: 4
      24. [-1, -1, 1]    DISTANCE: 3                    56. [2, 1, 1]      DISTANCE: 4
      25. [-1, 0, 2]     DISTANCE: 3                    57. [2, 2, 0]      DISTANCE: 4
      26. [-1, 1, -1]    DISTANCE: 3                    58. [-1, 2, 2]     DISTANCE: 5
      27. [-1, 1, 1]     DISTANCE: 3                    59. [1, 2, 2]      DISTANCE: 5
      28. [-1, 2, 0]     DISTANCE: 3                    60. [2, -1, 2]     DISTANCE: 5
      29. [0, -1, 2]     DISTANCE: 3                    61. [2, 1, 2]      DISTANCE: 5
      30. [0, 1, 2]      DISTANCE: 3                    62. [2, 2, -1]     DISTANCE: 5
      31. [0, 2, -1]     DISTANCE: 3                    63. [2, 2, 1]      DISTANCE: 5
      32. [0, 2, 1]      DISTANCE: 3                    64. [2, 2, 2]      DISTANCE: 6
      

      这个解决方案的好处是不需要任何特殊的数据结构,甚至不需要数组。


      如果您可以使用队列(使用数组不难实现)和 3D 布尔(或 int)数组就像从中心开始的 BFS 一样,则可能是另一种解决方案。

      首先,定义什么是邻居,可以使用移动数组,例如:

      • 如果共享一个公共边(Manhattan distance),则两个单元格是邻居:

         DX = { 1, 0, 0, -1, 0, 0 }
         DY = { 0, 1, 0, 0, -1, 0 }
         DZ = { 0, 0, 1, 0, 0, -1 }
        
      • 如果共享一条边,则两个单元格是邻居:

         DX = { 1, 0, 0, -1, 0, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0 }
         DY = { 0, 1, 0, 0, -1, 0, 1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0, -1 }
         DZ = { 0, 0, 1, 0, 0, -1, 0, 1, 1, 0, 1, 1, 0, -1, -1, 0, -1, -1 }
        
      • 如果共享一个角,则两个单元格是邻居 (Chebyshev distance):

         DX = { 1, 0, 0, -1, 0, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 1, 1, -1, -1, 1, -1 }
         DY = { 0, 1, 0, 0, -1, 0, 1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0, -1, 1, 1, -1, 1, -1, 1, -1, -1 }
         DZ = { 0, 0, 1, 0, 0, -1, 0, 1, 1, 0, 1, 1, 0, -1, -1, 0, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1 }
        

      现在,使用队列,您可以从中心位置开始,然后添加邻居,然后是邻居的邻居,依此类推。在每次迭代中,您都可以访问每个生成的位置。

      类似这样的:

      DX = { 1, 0, 0, -1, 0, 0 }
      DY = { 0, 1, 0, 0, -1, 0 }
      DZ = { 0, 0, 1, 0, 0, -1 }
      
      VISIT_COORDS_FOR_DIM(L):
          LET N = L/2
          IF (REM(L, 2) == 0)
              N--
      
          V: BOOLEAN[L, L, L]
          Q: QUEUE<POINT3D>
      
          V[N, N, N] = TRUE
          ENQUEUE(Q, POINT3D(N, N, N))
      
          WHILE COUNT(Q) > 0
              P = DEQUEUE(Q)
              VISIT(P.X - N, P.Y - N, P.Z - N) //To Transform coords to [-N, N] range.
      
              FOR I = 0 TO LENGTH(DX) - 1
                  LET X = P.X + DX[I]
                  LET Y = P.Y + DY[I]
                  LET Z = P.Z + DZ[I]
      
                  IF IS_VALID_POS(L, X, Y, Z) && V[X, Y, Z] == FALSE
                      V[X, Y, Z] = TRUE
                      ENQUEUE(Q, POINT3D(X, Y, Z))
      
      IS_VALID_POS(L, X, Y, Z)
          RETURN X >= 0 && X < L &&
                 Y >= 0 && Y < L &&
                 Z >= 0 && Z < L
      

      用到的函数:

      REM(X, Y): Remainder after division of X by Y
      ENQUEUE(Q, X): Enqueue element X in queue Q
      DEQUEUE(Q): Dequeue first element from queue Q
      COUNT(Q): Number of elements in queue Q 
      
      VISIT(X, Y, Z): Visit the generated coordinate (X, Y, Z)
      

      此解决方案的好处是,您可以使用移动数组定义两个位置何时是邻居。

      【讨论】:

      • @Dual:如果您需要具体语言的实现,请告诉我。
      • 如果立方体有偶数边,零坐标表示什么?
      • @DaveGalvin:你需要定义它是在前半部分还是后半部分,在我的例子中,零坐标是前半部分的最后一个位置。
      • 第一个解决方案是我正在寻找的答案。谢谢
      • @m69 其他有类似问题的人可以从第二种解决方案中受益。所以没有伤害;)
      【解决方案4】:

      获得解决这个问题的有效算法的关键是查看其背后的几何形状。您要求的是为 N 的每个连续值求解 Diophantine equation N = a^2 + b^2 + c^2,并以任何顺序枚举此类解决方案。这个方程的解是半径为 N 的球体上的积分点。所以从某种意义上说,你的问题是枚举球体。

      不过,首先应该清楚的是,这里的难题是枚举坐标 (a,b,c) 的非负解。对于每个这样的坐标,围绕坐标平面的镜像对称还有八个其他解决方案,因为 a^2 = (-a)^2 等。(通常。如果 a、b、c 中的一个或多个为零,则得到更少的镜像点。)通过置换坐标使a

      球体枚举算法的本质是考虑两组点,它们近似于一个半径为 N 的球体:一组由范数“略小于”N 的点组成,另一组由范数为“ “略大于”或等于 N。“略小于”是指对于一个点 (a,b,c),a^2 + b^2 + c^2 = N。就代码而言,您无需表示“略少“ 放;它已经被处理了。创建一堆“稍微大一点”的集合就足够了,按它们的标准排序。

      算法的每一步都将 N 的“稍大”设置变为 N+1 的一个。删除堆的最小元素,例如 (a,b,c)。现在将具有更大范数的最近邻点添加到堆中,即三个点 (a+1,b,c)、(a,b+1,c) 和 (a,b,c+1)。其中一些可能已经存在;我会回到那个。当您在堆上添加一个增量点时,您需要它的规范。您确实不,但是,需要从头开始计算它。依靠恒等式 (a+1)^2 - a^2 = 2a + 1。换句话说,您不需要任何乘法运算来计算这些范数。根据您的 GPU,您可以计算表达式 a

      您还可以优化对堆上现有点的检查。每个点有六个直接的格子邻居。具有最小范数的格邻居将是第一个添加它的格子。假设点 (a,b,c) 的 a

      此算法枚举球体,而不是立方体。将注意力限制在具有最大索引 D 的立方体上是很容易的。如果其中一个坐标等于 D,则不要添加三个点,而是添加更少的点。枚举在点 (D,D,D) 处结束,此时没有更多有效的邻居点要添加。

      这个算法的性能应该非常快。它需要一个大小为 O(N^2) 的堆。如果您事先枚举所有点,则需要存储 O(N^3)。此外,它不需要乘法,以进一步恒定加速。

      【讨论】:

        【解决方案5】:

        如果只是中心:有很多不同的有效订单。只需计算一个 3d 地图,其中的元素按顺序排序。按原点偏移。制作地图:

        for x,y,z -domain, domain
          map.add ( x,y,z, distance(x,y,z) ) 
        map.sort ( distance ) 
        

        然后在x,y,z点遍历

        for ( i=0; i++ )
           visit ( map[i].xyz + x,y,z )
        

        如果它是真实距离而不是体素中心,则会变得更加困难。

        【讨论】:

        • 这是一个有效的解决方案,尽管它是某种预先计算的查找表,不符合我的要求(动态计算地图也不是选项,因为这需要太多时间来处理) .
        • @Dual:为什么地图不适合你?真诚好奇。然后我会做某种螺旋式...但是您将多次访问同一个单元格,并且可能不会访问所有单元格。如果您不关心距离度量,请查找 z-order/morton 曲线。每个象限做一个。
        • 是的,阅读其他一些答案,您真的只需要 z 阶曲线。对每个象限都这样做。 Z 顺序是 z(i) = ( every3rdbit(i), every3rdbit(i>>1), every3rdbit(i>>2) ) with every3rdbit (x) ( y=0; for(64) y+=(x&1); y>3;
        【解决方案6】:

        以曼哈顿距离顺序生成索引类似于子集总和问题,因此只需计算最大距离(总和),然后分离轴以减少问题。这里以C++为例:

        int x,y,z,d,dx,dy,dz,D;
        // center idx
        int cx=n>>1;
        int cy=n>>1;
        int cz=n>>1;
        // min idx
        int x0=-cx;
        int y0=-cy;
        int z0=-cz;
        // max idx
        int x1=n-1-cx;
        int y1=n-1-cy;
        int z1=n-1-cz;
        // max distance
        x=max(x0,x1);
        y=max(y0,y1);
        z=max(z0,z1);
        D=x+y+z;
        // here do your stuff
        #define traverse(x,y,z) { /* do something with the array beware x,y,z are signed !!!  */ }
        // traversal
        for (d=0;d<=D;d++)  // distance
         for (dx=d              ,x=-dx;x<=dx;x++) if ((x>=x0)&&(x<=x1)) // x axis separation
         for (dy=d-abs(x)       ,y=-dy;y<=dy;y++) if ((y>=y0)&&(y<=y1)) // y axis separation
            {
            dz=d-abs(x)-abs(y); // z axis have only 1 or 2 options
            z=-dz; if       (z>=z0)  traverse(x,y,z);
            z=+dz; if ((z)&&(z<=z1)) traverse(x,y,z);
            }
        #undef traverse
        

        您可以将traverse(x,y,z) 宏替换为您想要的任何内容或功能。当心x,y,z 已签名,因此可能是否定的以获取您需要使用(x+cx,y+cy,z+cz) 的C++ 样式索引。

        这可以处理展位偶数和奇数索引以及非立方体分辨率(如果您在前几个常量计算中简单地将n 转换为nx,ny,nz)。 [0,0,0] 也可以无处不在(不在中心),因此它很容易适用于我能想到的任何需求......

        这里是n=5的示例输出

        [ 0, 0, 0] = 0
        [-1, 0, 0] = 1
        [ 0,-1, 0] = 1
        [ 0, 0,-1] = 1
        [ 0, 0, 1] = 1
        [ 0, 1, 0] = 1
        [ 1, 0, 0] = 1
        [-2, 0, 0] = 2
        [-1,-1, 0] = 2
        [-1, 0,-1] = 2
        [-1, 0, 1] = 2
        [-1, 1, 0] = 2
        [ 0,-2, 0] = 2
        [ 0,-1,-1] = 2
        [ 0,-1, 1] = 2
        [ 0, 0,-2] = 2
        [ 0, 0, 2] = 2
        [ 0, 1,-1] = 2
        [ 0, 1, 1] = 2
        [ 0, 2, 0] = 2
        [ 1,-1, 0] = 2
        [ 1, 0,-1] = 2
        [ 1, 0, 1] = 2
        [ 1, 1, 0] = 2
        [ 2, 0, 0] = 2
        [-2,-1, 0] = 3
        [-2, 0,-1] = 3
        [-2, 0, 1] = 3
        [-2, 1, 0] = 3
        [-1,-2, 0] = 3
        [-1,-1,-1] = 3
        [-1,-1, 1] = 3
        [-1, 0,-2] = 3
        [-1, 0, 2] = 3
        [-1, 1,-1] = 3
        [-1, 1, 1] = 3
        [-1, 2, 0] = 3
        [ 0,-2,-1] = 3
        [ 0,-2, 1] = 3
        [ 0,-1,-2] = 3
        [ 0,-1, 2] = 3
        [ 0, 1,-2] = 3
        [ 0, 1, 2] = 3
        [ 0, 2,-1] = 3
        [ 0, 2, 1] = 3
        [ 1,-2, 0] = 3
        [ 1,-1,-1] = 3
        [ 1,-1, 1] = 3
        [ 1, 0,-2] = 3
        [ 1, 0, 2] = 3
        [ 1, 1,-1] = 3
        [ 1, 1, 1] = 3
        [ 1, 2, 0] = 3
        [ 2,-1, 0] = 3
        [ 2, 0,-1] = 3
        [ 2, 0, 1] = 3
        [ 2, 1, 0] = 3
        [-2,-2, 0] = 4
        [-2,-1,-1] = 4
        [-2,-1, 1] = 4
        [-2, 0,-2] = 4
        [-2, 0, 2] = 4
        [-2, 1,-1] = 4
        [-2, 1, 1] = 4
        [-2, 2, 0] = 4
        [-1,-2,-1] = 4
        [-1,-2, 1] = 4
        [-1,-1,-2] = 4
        [-1,-1, 2] = 4
        [-1, 1,-2] = 4
        [-1, 1, 2] = 4
        [-1, 2,-1] = 4
        [-1, 2, 1] = 4
        [ 0,-2,-2] = 4
        [ 0,-2, 2] = 4
        [ 0, 2,-2] = 4
        [ 0, 2, 2] = 4
        [ 1,-2,-1] = 4
        [ 1,-2, 1] = 4
        [ 1,-1,-2] = 4
        [ 1,-1, 2] = 4
        [ 1, 1,-2] = 4
        [ 1, 1, 2] = 4
        [ 1, 2,-1] = 4
        [ 1, 2, 1] = 4
        [ 2,-2, 0] = 4
        [ 2,-1,-1] = 4
        [ 2,-1, 1] = 4
        [ 2, 0,-2] = 4
        [ 2, 0, 2] = 4
        [ 2, 1,-1] = 4
        [ 2, 1, 1] = 4
        [ 2, 2, 0] = 4
        [-2,-2,-1] = 5
        [-2,-2, 1] = 5
        [-2,-1,-2] = 5
        [-2,-1, 2] = 5
        [-2, 1,-2] = 5
        [-2, 1, 2] = 5
        [-2, 2,-1] = 5
        [-2, 2, 1] = 5
        [-1,-2,-2] = 5
        [-1,-2, 2] = 5
        [-1, 2,-2] = 5
        [-1, 2, 2] = 5
        [ 1,-2,-2] = 5
        [ 1,-2, 2] = 5
        [ 1, 2,-2] = 5
        [ 1, 2, 2] = 5
        [ 2,-2,-1] = 5
        [ 2,-2, 1] = 5
        [ 2,-1,-2] = 5
        [ 2,-1, 2] = 5
        [ 2, 1,-2] = 5
        [ 2, 1, 2] = 5
        [ 2, 2,-1] = 5
        [ 2, 2, 1] = 5
        [-2,-2,-2] = 6
        [-2,-2, 2] = 6
        [-2, 2,-2] = 6
        [-2, 2, 2] = 6
        [ 2,-2,-2] = 6
        [ 2,-2, 2] = 6
        [ 2, 2,-2] = 6
        [ 2, 2, 2] = 6
        

        【讨论】:

          【解决方案7】:

          已知每个点(a, b, c) 到原点的距离为sqrt(a*a + b*b + c*c)。我们可以将其定义为distance(a, b, c)。†

          对于 3D 数组中的每个点,您可以使用 distance 作为排序标准将其插入到 min heap 中。为避免重新计算,请增加堆中的点表示,以包括 distance 在插入堆时的缓存计算。

          heap_element = (x, y, z, d)

          heap_compare(heap_element a, heap_element b) = a.d

          对于 3D 数组中的每个点 (x,y,z)
          · heap.add(heap_element(x, y, z, distance(x, y, z)))

          现在,您可以从堆的顶部提取每个点来获得您的排序。

          N = 堆大小
          对于 i in 0..N
          · ordering[i] = heap.top
          · heap.pop

          † 就本算法而言,使用实际距离并不重要。出于性能原因,您可以省略使用sqrt,而只使用a*a + b*b + c*c 作为堆排序标准的度量。

          【讨论】:

            【解决方案8】:

            在 ruby​​ 中,我只是按顺序获取距中心的每个距离的所有点。

            def get_points(side_len)
              side_len % 2 == 0 ? min_dist = 1 : min_dist = 0
            
              if side_len % 2 == 0
                min_dist = 1
                max_1d_dist = side_len / 2
              else
                min_dist = 0
                max_1d_dist = (side_len - 1) / 2
              end
            
              max_dist = 3 * max_1d_dist
            
              min_dist.upto(max_dist) do |dist|
                min_x_dist = [min_dist, dist - 2 * max_1d_dist].max
                max_x_dist = [dist - 2 * min_dist, max_1d_dist].min
                min_x_dist.upto(max_x_dist) do |x_dist|
                  min_y_dist = [min_dist, dist - x_dist - max_1d_dist].max
                  max_y_dist = [dist - x_dist - min_dist, max_1d_dist].min
                  min_y_dist.upto(max_y_dist) do |y_dist|
                    z_dist = dist - x_dist - y_dist
                    print_vals(x_dist, y_dist, z_dist)
                  end
                end
              end
            end
            
            def print_vals(x_dist, y_dist, z_dist)
              x_signs = [1]
              y_signs = [1]
              z_signs = [1]
              x_signs << -1 unless x_dist == 0
              y_signs << -1 unless y_dist == 0
              z_signs << -1 unless z_dist == 0
            
              x_signs.each do |x_sign|
                y_signs.each do |y_sign|
                  z_signs.each do |z_sign|
                    puts "[#{x_sign*x_dist}, #{y_sign*y_dist}, #{z_sign*z_dist}]"
                  end
                end
              end
            end
            

            输出是:

            2.1.2 :277 > get_points(1)
            [0, 0, 0]
            
            2.1.2 :278 > get_points(2)
            [1, 1, 1]
            [1, 1, -1]
            [1, -1, 1]
            [1, -1, -1]
            [-1, 1, 1]
            [-1, 1, -1]
            [-1, -1, 1]
            [-1, -1, -1]
            
            2.1.2 :279 > get_points(3)
            [0, 0, 0]
            [0, 0, 1]
            [0, 0, -1]
            [0, 1, 0]
            [0, -1, 0]
            [1, 0, 0]
            [-1, 0, 0]
            [0, 1, 1]
            [0, 1, -1]
            [0, -1, 1]
            [0, -1, -1]
            [1, 0, 1]
            [1, 0, -1]
            [-1, 0, 1]
            [-1, 0, -1]
            [1, 1, 0]
            [1, -1, 0]
            [-1, 1, 0]
            [-1, -1, 0]
            [1, 1, 1]
            [1, 1, -1]
            [1, -1, 1]
            [1, -1, -1]
            [-1, 1, 1]
            [-1, 1, -1]
            [-1, -1, 1]
            [-1, -1, -1]
            

            【讨论】:

              【解决方案9】:

              这是一种简单且快速算法,可使用曼哈顿距离横穿 3D 阵列。

              每个维度中大小为n 的3D 数组应由坐标系表示,坐标系位于数组中间。对于要定义的中心,我们假设每个维度中数组的大小是奇数。每个元素都有像[x, y, z] 这样的三个坐标,每个坐标可以达到最大值`(n/2)-1。 (信息:添加的图片是二维的,以便更好地理解)

              1. 首先我们可以通过只考虑正八分圆来简化这一点(所有坐标都是正的)。所有其他元素都可以通过反射生成。
              2. 在一个八分圆中,与中心距离相同的所有元素都由一个平面定义,其方程为:x+y+z=distance。我们通过单步计算从0 到n-1 的距离来实现这一点。对于每个距离,我们都会在相应平面上查找所有元素。
              3. 当到达distance&gt;(n/2)-1 时,一些点将位于数组之外(coord &gt; (n/2)-1 之一)。所以我们必须从结果中排除它们。
              4. 每个计算出的元素最多代表您通过反射获得的 8 个元素(参见第 1 点)。您可以通过将每个坐标交替乘以-1 来简单地实现这一点。 [+/-x, +/-y, +/-z](如果全部是coords != 0,则有 8 种可能的组合)

              这是我的算法的代码架构:

              //rise the distance by one each iteration
              for(distance=0; distance<n-1; distance++) //loop distance from 0 to n-1
                for(i=0; i<=distance; i++) 
                  x=i; //x ∈ [0, distance]
                  for(j=0; j<=distance-x; j++) 
                    y=j; //y ∈ [0, distance-x]
                    z=distance-(x+y); //because distance=x+y+z
                    //now we have to exclude all elements with one coord <= (n/2)-1
                    if(x<=(n/2)-1 && y<=(n/2)-1 && z<=(n/2)-1)
                      //[x,y,z] we found a valid element!
                      //let's generate the 7 corresponding elements (up to 7)
                      if(x!=0) //[-x,y,z]
                      if(y!=0) //[x,-y,z]
                      if(z!=0) //[x,y,-z]
                      if(x!=0 && y!=0) //[-x,-y,z]
                      if(x!=0 && z!=0) //[-x,y,-z]
                      if(y!=0 && z!=0) //[x,-y,-z]
                      if(y!=0 && y!=0 && z!=0) //[-x,-y,-z]
              

              这是n=7 的输出:

              Distance:0 [0,0,0] 
              Distance:1 [0,0,1] [0,0,-1] [0,1,0] [0,-1,0] [1,0,0] [-1,0,0] 
              Distance:2 [0,0,2] [0,0,-2] [0,1,1] [0,-1,1] [0,1,-1] [0,-1,-1] [0,2,0] [0,-2,0] [1,0,1] [-1,0,1] [1,0,-1] [-1,0,-1] [1,1,0] [-1,1,0] [1,-1,0] [-1,-1,0] [2,0,0] [-2,0,0] 
              Distance:3 [0,1,2] [0,-1,2] [0,1,-2] [0,-1,-2] [0,2,1] [0,-2,1] [0,2,-1] [0,-2,-1] [1,0,2] [-1,0,2] [1,0,-2] [-1,0,-2] [1,1,1] [-1,1,1] [1,-1,1] [1,1,-1] [-1,-1,1] [-1,1,-1] [1,-1,-1] [-1,-1,-1] [1,2,0] [-1,2,0] [1,-2,0] [-1,-2,0] [2,0,1] [-2,0,1] [2,0,-1] [-2,0,-1] [2,1,0] [-2,1,0] [2,-1,0] [-2,-1,0] 
              Distance:4 [0,2,2] [0,-2,2] [0,2,-2] [0,-2,-2] [1,1,2] [-1,1,2] [1,-1,2] [1,1,-2] [-1,-1,2] [-1,1,-2] [1,-1,-2] [-1,-1,-2] [1,2,1] [-1,2,1] [1,-2,1] [1,2,-1] [-1,-2,1] [-1,2,-1] [1,-2,-1] [-1,-2,-1] [2,0,2] [-2,0,2] [2,0,-2] [-2,0,-2] [2,1,1] [-2,1,1] [2,-1,1] [2,1,-1] [-2,-1,1] [-2,1,-1] [2,-1,-1] [-2,-1,-1] [2,2,0] [-2,2,0] [2,-2,0] [-2,-2,0] 
              Distance:5 [1,2,2] [-1,2,2] [1,-2,2] [1,2,-2] [-1,-2,2] [-1,2,-2] [1,-2,-2] [-1,-2,-2] [2,1,2] [-2,1,2] [2,-1,2] [2,1,-2] [-2,-1,2] [-2,1,-2] [2,-1,-2] [-2,-1,-2] [2,2,1] [-2,2,1] [2,-2,1] [2,2,-1] [-2,-2,1] [-2,2,-1] [2,-2,-1] 
              

              如果您使用的是欧几里得范数,则必须将您的距离替换为:sqrt(x*x+y*y+z*z),并且您不能以一为单位增加距离。但除此之外,您可以非常相似地做到这一点。

              【讨论】:

                【解决方案10】:

                等到赏金赛结束,以免干扰。但是,我想指出,我可以看到 atm 的恕我直言 所有答案 相当可疑。问题持有这样的说法:

                “此算法旨在在 GPU 上执行”

                确实可以在 GPU 上进行高效的数值处理,但这里的所有解决方案都提出了某种循环。这不是 GPU 的工作方式。

                GPU 并行执行。确实可以在 GPU 中循环,但它以一种极其孤立的方式发生。循环需要一个“全局控制器”,例如计数器,但 GPU 执行的重点是它使用多个内核以没有特定顺序执行。不可能“抓取”一个核心的执行结果并将其用作另一个核心计算的参数。这根本不是 GPU 的工作方式。

                可以在 GPU 上进行多维数组的计算,甚至在高于 3 维的情况下。这只是单元寻址的问题,使用一维地址空间来寻址例如 4 维并描述/访问相应的数据是微不足道的。

                但是不可能以特定的顺序逐个遍历单元(像素、内存位置) - 无法控制。并且不可能递增,或者有循环,或者有内部循环。在 GPU 上,最后一个元素很可能首先被执行。在 GPU 上,每个元素都是完全自主的。没有计算知道任何其他计算,因为它们之间没有通信。 GPU 上每次计算的环境就像世界上只有它一个人。

                此线程中给出的答案都假设 CPU 逻辑。如果解决CPU上的问题,那是微不足道的。以二维为例,这里是 5x5 的绝对 x,y 坐标对,中间调整为零:

                ┌───┬───┬───┬───┬───┐
                │2 2│2 1│2 0│2 1│2 2│
                ├───┼───┼───┼───┼───┤
                │1 2│1 1│1 0│1 1│1 2│
                ├───┼───┼───┼───┼───┤
                │0 2│0 1│0 0│0 1│0 2│
                ├───┼───┼───┼───┼───┤
                │1 2│1 1│1 0│1 1│1 2│
                ├───┼───┼───┼───┼───┤
                │2 2│2 1│2 0│2 1│2 2│
                └───┴───┴───┴───┴───┘
                

                在每个单元格中添加对给我们曼哈顿距离:

                4 3 2 3 4
                3 2 1 2 3
                2 1 0 1 2
                3 2 1 2 3
                4 3 2 3 4
                

                同时,我们可以有一个索引系统(不过这是问题的一个弱点,因为它没有状态寻址空间):

                 1  2  3  4  5
                 6  7  8  9 10
                11 12 13 14 15
                16 17 18 19 20
                21 22 23 24 25
                

                我们可以使两个距离变平:

                dist = 4 3 2 3 4 3 2 1 2 3 2 1 0 1 2 3 2 1 2 3 4 3 2 3 4
                

                和索引

                index = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
                

                N(或 N * N 或 N * N * N)数组的唯一曼哈顿距离是1 到 N-1 之间的所有数字,如果 N 为奇数,则前面为 0。对于 5*5 矩阵:

                0 1 2 3 4
                

                遍历每个距离,看看该距离等于“dist”的位置。对于 5*5:

                0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 // dist = 0 at locations 13
                0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0 // dist = 1 at locations 8 12 14 18
                0 0 1 0 0 0 1 0 1 0 1 0 0 0 1 0 1 0 1 0 0 0 1 0 0 // dist = 2 at locations 3 7 9 11 15 17 19 23
                0 1 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 1 0 // dist = 3 at locations 2 4 6 10 16 20 22 24
                1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 // dist = 4 at locations 1 5 21 25
                

                并根据这些位置建立一个数组。 IE。有一个空数组,将 13 作为第一个元素附加到它,然后附加 8 12 14 18 等。最终结果将是

                13 8 12 14 18 3 7 9 11 15 17 19 23 2 4 6 10 16 20 22 24 1 5 21 25 
                

                这是所需的排序顺序。通过使用除法、最小值和余数,可以很简单地将其重新排列到例如二维地址空间中。

                但是,这种计算方式在 GPU 上是无用的。它需要特定的执行顺序,而 我们在 GPU 上没有。

                如果在 GPU 上解决遍历顺序,解决方案应该是

                lookupcoordinates = fn(当前位置)

                也许可以描述 fn,但这个问题太不完整了——需要更多的细节,而不是仅仅说“在 GPU 上”。 3d 数组是如何描述的?它位于什么记忆中?结果的寻址空间是什么?等等。

                我很高兴听到更多关于这方面的见解细节,以及对我上面所写内容的更正。


                与用户m69讨论的附录

                一个(次要)想法是在 [维数] 步骤中进行计算,其中前一维的累积将用于下一个,例如 3。可能是数值行为在这样的情况下很有用一个案例。

                如果仔细研究基础知识,可能会假设一个线性目标空间,这样它就是一个从 1 到 [单元数] 的简单索引向量,例如,一个 10*10*10 的立方体将具有线性包含 1000 个索引的一维向量,从 1 到 1000。以后总是可以将这些索引重新表述为方形或更多维格式。

                在一维情况下,假设我们有一个 9 元素数据“立方体”(如果以 3 维表示,则与 9*1*1 相同)。如下所示

                x x x x x x x x x // Data
                1 2 3 4 5 6 7 8 9 // Indexes
                4 3 2 1 0 1 2 3 4 // Manhattan distances
                5 4 6 3 7 2 8 1 9 // Pick order, starting from center
                

                因此我们需要一个映射如下的fn

                ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
                │1 to 5│2 to 4│3 to 6│4 to 3│5 to 7│6 to 2│7 to 8│8 to 1│9 to 9│
                └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
                

                现在,如果例如查看结果的索引 4,fn 必须能够独立于任何其他索引来解析结果 = 3,即。 fn(4) = 3。应该可以描述 fn。应该可以首先得出结论,“4”位于第 2 层(如果第 0 层是最里面的)。之后,应该可以得出结论第 2 层有多少个单元(所有层都有 2 个单元),最后这个单元是第 2 层的第一个还是第二个出现/元素。这将分解为 3,即。对于结果[4],我们选择数据[3]。

                现在,如果假设一个大小为 11*11(*1) 的二维“立方体”,我们会遇到这种情况:

                0 1 2  3  4  5  6  7  8 9 10 // Unique Manhattan distances
                1 4 8 12 16 20 20 16 12 8  4 // How many cells in each distance-layer?
                

                我们注意到“多少”是相当对称的,对于 10*10 甚至更加对称:

                1  2  3  4  5  6  7  8   9 // Unique Manhattan distances
                4  8 12 16 20 16 12  8   4 // How many cells in each distance-layer?
                4 12 24 40 60 76 88 96 100 // Cumulative sum of "How many..."
                

                注意“累计”!使用它,如果我们正在 atm 求解例如 index=55(可能发生在任何时间,在 54 之前或之后,请记住!),我们可以得出结论,我们当前的目标是第 5 层,它包含 20 个元素,那些索引 = 40...60。

                这一层从 40 开始,我们现在是 55。差是 15。也许可以描述 offset (x,y)-coordinates from "layer 5-origin x, y) 使用那个“15”?我猜我们刚刚进入第四象限。

                3D 也一样?

                【讨论】:

                • 我们确实无法控制全局的执行顺序,但是当涉及到单个内核的执行时,我们可以控制每个内核访问其邻居(如果需要)或任意内存的顺序。但是,您是对的,这可能不是最佳解决方案,因为这种方法可能会导致分支和数据分歧,从而限制 GPU 的性能。当使用常量值时,一些处理 for 循环的给定答案(包括已接受的答案)可能会在编译时展开,这至少会消除分支分歧。
                • 确实循环会被取消,通常是默认的。但是该循环仍然驻留在单个内核(或迭代,内核)中。为了获得 GPU 并行性的优势,所有迭代(w 或 w/o 内部循环)都应该同时执行,并且正确执行。我编写了很多 GPGPU,我的第一反应是离线预先计算排序顺序,并根据需要(重新)使用它。如果数据大小差异很大,则存在缺陷。然而,我确实认为有一种方法可以描述直接 lookup=fn(x,y,z),解决 fn 并不简单。但我确信它确实存在。
                • 那么 GPU 对 3D 数组中的某些单元格做某事的方法是创建第二个 3D 数组作为掩码,然后将它们组合起来吗?您能否使用某种“相邻像素”方法从前一个 2D 图层创建这样的蒙版中的每个 2D 图层?
                • 第一次同意,第二次拒绝。通常在 GPU 上处理可能涉及一些帮助步骤,这些是“中间阶段计算”,但必须注意它们也是并行发生的。 IE。一个步骤可以是“计算完整集 A”,另一个“完整集 B”,最后计算 C=fn(A, B)。但是,当您解析 C 时,您无法访问 C 中的“相邻部分结果”(因为没有执行顺序,并且 C 本身处于“只写”状态),这带来了巨大的挑战。但是,您可以在计算 C 时访问 A 和/或 B 中的任何内容。继续...
                • 如果我正确理解了挑战,这实际上是将寻址从一个空间转换为另一个空间。一维和二维之间的转换很简单,比如 1,2,3,... 到 (1,1),(1,2)(2,1).... 但这是(我猜) 更像是从上升的一维(或至少平方)转换为“上升的八面体分层”空间,当“上升”意味着“最内层优先”时,除了每个层表面上现有的(尽管是任意的)递增顺序.确实很棘手:-)(编辑:请注意,您的解决方案非常好,只是不太在这个棘手的盒子里:-))
                猜你喜欢
                • 1970-01-01
                • 2014-07-29
                • 2019-09-18
                • 2020-09-25
                • 1970-01-01
                • 2014-02-12
                • 2010-12-14
                • 2016-01-31
                相关资源
                最近更新 更多