【问题标题】:Searching a 3D array for closest point satisfying a certain predicate在 3D 数组中搜索满足某个谓词的最近点
【发布时间】:2017-12-07 16:30:05
【问题描述】:

我正在寻找一种枚举算法来搜索围绕给定起点“旋转”的 3D 数组。

给定一个大小为NxNxN 的数组a,其中每个N2^k,对于某些k,以及该数组中的一个点p。我正在寻找的算法应该执行以下操作:如果a[p] 满足某个谓词,则算法停止并返回p。否则检查下一个点q,其中q 是数组中最接近p 并且尚未访问过的另一个点。如果这也不匹配,则检查下一个q',以此类推,直到在最坏的情况下搜索整个数组。

这里的“最近”是完美的解决方案是点qp 的欧几里得距离最小。由于只需要考虑离散点,也许一些聪明的枚举算法 woukd 使这成为可能。但是,如果这变得太复杂,那么最小的曼哈顿距离也可以。如果有几个最近的点,那么接下来应该考虑哪一个都没有关系。

是否已有可用于此任务的算法?

【问题讨论】:

  • 您正在混合任务和解决方案。并且您固定的解决方案部分不适合这个问题。谁说过一步一步你会找到最近的“好”细胞?相反,下一个距离层的单元格将大部分被分离。
  • @fafl:我查看了 R-tres,但我不知道如何使用它为给定的 p 找到最近的 q。 AFAIU,在 R-tree 中搜索决定了某个分支,然后会在该分支中找到最近的点,如果您选择了错误的分支,则不一定是所有分支中的最近点。

标签: algorithm search multidimensional-array


【解决方案1】:

您可以搜索增加的平方距离,这样您就不会错过任何一个点。这段python代码应该很清楚:

import math
import itertools

# Calculates all points at a certain distance.
# Coordinate constraint: z <= y <= x
def get_points_at_squared_euclidean_distance(d):
    result = []
    x = int(math.floor(math.sqrt(d)))
    while 0 <= x:
        y = x
        while 0 <= y:
            target = d - x*x - y*y
            lower = 0
            upper = y + 1
            while lower < upper:
                middle = (lower + upper) / 2
                current = middle * middle
                if current == target:
                    result.append((x, y, middle))
                    break
                if current < target:
                    lower = middle + 1
                else:
                    upper = middle
            y -= 1
        x -= 1
    return result

# Creates all possible reflections of a point
def get_point_reflections(point):
    result = set()
    for p in itertools.permutations(point):
        for n in range(8):
            result.add((
                p[0] * (1 if n % 8 < 4 else -1),
                p[1] * (1 if n % 4 < 2 else -1),
                p[2] * (1 if n % 2 < 1 else -1),
            ))
    return sorted(result)

# Enumerates all points around a center, in increasing distance
def get_next_point_near(center):
    d = 0
    points_at_d = []
    while True:
        while not points_at_d:
            d += 1
            points_at_d = get_points_at_squared_euclidean_distance(d)
        point = points_at_d.pop()
        for reflection in get_point_reflections(point):
            yield (
                center[0] + reflection[0],
                center[1] + reflection[1],
                center[2] + reflection[2],
            )

# The function you asked for
def get_nearest_point(center, predicate):
    for point in get_next_point_near(center):
        if predicate(point):
            return point

# Example usage
print get_nearest_point((1,2,3), lambda p: sum(p) == 10)

基本上,您会消耗生成器中的点,直到其中一个满足您的谓词。

【讨论】:

    【解决方案2】:

    这是一个简单算法的伪代码,它将在半径增加的球形外壳中搜索,直到找到一个点或用完数组。让我们假设 condition 返回 true 或 false 并且可以访问正在测试的 x、y、z 坐标和数组本身,对于越界坐标返回 false(而不是爆炸):

    def find_from_center(center, max_radius, condition) returns a point
      let radius = 0
      while radius < max_radius,
         let point = find_in_spherical_husk(center, radius, condition)
         if (point != null) return point
         radius ++
      return null
    

    困难的部分在find_in_spherical_husk 内部。我们有兴趣检查这样的点

    dist(center, p) >= radius AND dist(center, p) < radius+1
    

    这将是我们对 husk 的操作定义。我们可以在 O(n^3) 中遍历整个 3D 数组来寻找那些,但这在时间方面确实很昂贵。更好的伪代码如下:

    def find_in_spherical_husk(center, radius, condition)
       let z = center.z - radius // current slice height
       let r = 0 // current circle radius; maxes at equator, then decreases
       while z <= center + radius,
         let z_center = (z, center.x, point.y)  
         let point = find_in_z_circle(z_center, r)
         if (point != null) return point
         // prepare for next z-sliced cirle
         z ++
         r = sqrt(radius*radius - (z-center.z)*(z-center.z)) 
    

    这里的想法是将每个外壳沿 z 轴切成圆形(任何轴都可以),然后分别查看每个切片。如果你看着地球,两极是 z 轴,你会从北向南切片。最后,您将实现find_in_z_circle(z_center, r, condition) 来查看每个圆圈的周长。您可以使用Bresenham circle-drawing algorithm 避免一些数学运算;但我认为与检查condition 的成本相比,节省的成本可以忽略不计。

    【讨论】:

    • 如果您不使用距离,而是使用它们的正方形,则层内查找将更加简单。 Square 是一个不断增长的函数,因此您不会有任何损失。
    • @Gangnus 是的,但总体上节省了一点时间。最大的好处是它通过坚持整数、无损数学来避免潜在的与精度相关的错误
    • 当然。那是因为我用这种方式进行距离检查已经 39 年了。
    • @tucuxi:谢谢,到目前为止,您的算法似乎可以使用更简单的“find_in_z_square”函数来检查点是否在围绕中心的正方形表面而不是圆形表面。我有点担心,如果使用像 Bresenham 这样的圆算法,两个相邻半径之间可能会遗漏一些点。这种算法会发生这种情况吗?
    • @siracusa 不,不应该有这样的担忧。只要您真正检查满足其距离为&gt;= r&lt; r+1 的点(或者,为了避免浮点和平方根,它们的距离平方在 r^2 和 (r+1)^ 之间2 - 正如 Gangus 所指出的那样),确实没有办法让积分丢失或重复计算。 Bresenham 只是避免了额外的数学运算,但 IIRC 是通过将误差项严格保持在所需范围内来做到这一点的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-25
    • 1970-01-01
    • 2016-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多