如何有效地枚举n维网格中的所有球面点

时间:2012-06-22 19:19:46

标签: algorithm

说,我们有一个N维网格,其中有一些点X,坐标为(x1,x2,...,xN)。 为简单起见,我们可以假设网格是无界的。

让半径为R,这个半径的球体以X为中心,即网格中所有点的集合,使得它们与曼哈顿的距离等于R.

我怀疑他们会有2 * N * R这样的分数。

我的问题是:如何以高效和简单的方式枚举它们?通过“枚举”我的意思是算法,给定N,X和R将产生形成该球体的点列表(其中point是它的坐标列表)。

更新:我最初调用了我误用“汉明距离”的指标。我向所有回答这个问题的人道歉。感谢Steve Jessop指出这一点。

3 个答案:

答案 0 :(得分:4)

考虑绑定超球面的最小轴对齐超立方体,并编写一个程序来枚举超立方体内的网格点。

然后你只需要一个简单的过滤功能,它允许你丢弃立方体上但不在超球面上的点。

对于小尺寸,这是一种简单而有效的解决方案。例如,对于2D,将丢弃为边界正方形枚举的20%的点;对于6D,几乎90%的超立方点都被丢弃了。

对于更高的维度,您将不得不使用更复杂的方法:遍历每个维度(如果维度的数量是可变的,您可能需要使用递归函数)。对于每个循环,您必须根据已计算的网格组件的值调整最小值和最大值。好吧,尝试用它做二维,枚举圆的点,一旦你理解了它,将程序推广到更高的维度就会非常简单。

更新:errh,等一下,你要使用曼哈顿距离。调用cross polytope“球体”可能是正确的,但我发现它很混乱!无论如何,您可以使用相同的方法。

如果您只想枚举交叉多面体的超曲面上的点,那么解决方案也非常相似,您必须以适当的限制遍历每个维度。例如:

for (i = 0; i <= n; i++)
  for (j = 0; j + i <= n; j++)
    ...
       for (l = 0; l + ...+ j + i <= n; l++) {
         m = n - l - ... - j - i;
         printf(pat, i, j, ..., l, m);
       }

对于以这种方式生成的每个点,您必须考虑所有因否定任何组件而导致覆盖所有面的变化,然后用向量X替换它们。

<强>更新

对于X = 0的情况的Perl实现:

#!/usr/bin/perl

use strict;
use warnings;

sub enumerate {
    my ($d, $r) = @_;

    if ($d == 1) {
        return ($r ? ([-$r], [$r]) : [0])
    }
    else {
        my @r;
        for my $i (0..$r) {
            for my $s (enumerate($d - 1, $r - $i)) {
                for my $j ($i ? (-$i, $i) : 0) {
                    push @r, [@$s, $j]
                }
            }
        }
        return @r;
    }
}

@ARGV == 2 or die "Usage:\n  $0 dimension radius\n\n";
my ($d, $r) = @ARGV;
my @r = enumerate($d, $r);
print "[", join(',', @$_), "]\n" for @r;

答案 1 :(得分:1)

你可以从中心递归地工作,一次计算零距离并处理对称性。此Python实现适用于较低维度的“干”向量,并一次实现一个1维切片。人们也可能会反过来,但这意味着要重复部分超球面。虽然在数学上是相同的,但两种方法的效率都与语言密切相关。

如果您事先知道目标空间的基数,我建议您编写一个迭代实现。

以下列举了笔记本电脑上大约200毫秒的六维R = 16超乐高块上的点。当然,随着尺寸越来越大或球体越来越大,性能会迅速下降。

def lapp(lst, el):
    lst2 = list(lst)
    lst2.append(el)
    return lst2

def hypersphere(n, r, stem = [ ]):
    mystem  = lapp(stem, 0)
    if 1 == n:
            ret = [ mystem ]
            for d in range(1, r+1):
                    ret.append(lapp(stem,  d))
                    ret.append(lapp(stem, -d))
    else:
            ret     = hypersphere(n-1, r, mystem)
            for d in range(1, r+1):
                    mystem[-1] = d
                    ret.extend(hypersphere(n-1, r-d, mystem))
                    mystem[-1] = -d
                    ret.extend(hypersphere(n-1, r-d, mystem))
    return ret

(这种实现假设超球面位于原点的中心。之后转换所有点比携带中心坐标更容易。)

答案 2 :(得分:1)

输入:半径R,尺寸D

  • 生成基数的所有integer partitions的R
  • 对于每个分区,不加重复地置换
  • 对于每个排列,旋转所有标志

例如,python中的代码:

from itertools import *

# we have to write this function ourselves because python doesn't have it...
def partitions(n, maxSize):
    if n==0:
        yield []
    else:
        for p in partitions(n-1, maxSize):
            if len(p)<maxSize:
                yield [1] + p
            if p and (len(p)<2 or p[1]>p[0]):
                yield [ p[0]+1 ] + p[1:]

# MAIN CODE    
def points(R, D):
    for part in partitions(R,D):             # e.g. 4->[3,1]
        part = part + [0]*(D-len(part))      # e.g. [3,1]->[3,1,0]    (padding)
        for perm in set(permutations(part)): # e.g. [1,3,0], [1,0,3], ...
            for point in product(*[          # e.g. [1,3,0], [-1,3,0], [1,-3,0], [-...
                  ([-x,x] if x!=0 else [0]) for x in perm
                ]):
                yield point

演示半径= 4,尺寸= 3:

>>> result = list( points(4,3) )

>>> result
[(-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, 1), (-1, -1, -2), (-1, -1, 2), (-1, 1, -2), (-1, 1, 2), (1, -1, -2), (1, -1, 2), (1, 1, -2), (1, 1, 2), (0, -2, -2), (0, -2, 2), (0, 2, -2), (0, 2, 2), (-2, 0, -2), (-2, 0, 2), (2, 0, -2), (2, 0, 2), (-2, -2, 0), (-2, 2, 0), (2, -2, 0), (2, 2, 0), (-1, 0, -3), (-1, 0, 3), (1, 0, -3), (1, 0, 3), (-3, -1, 0), (-3, 1, 0), (3, -1, 0), (3, 1, 0), (0, -1, -3), (0, -1, 3), (0, 1, -3), (0, 1, 3), (-1, -3, 0), (-1, 3, 0), (1, -3, 0), (1, 3, 0), (-3, 0, -1), (-3, 0, 1), (3, 0, -1), (3, 0, 1), (0, -3, -1), (0, -3, 1), (0, 3, -1), (0, 3, 1), (0, -4, 0), (0, 4, 0), (0, 0, -4), (0, 0, 4), (-4, 0, 0), (4, 0, 0)]

>>> len(result)
66

(上面我使用set(permutations(...))来获得排列而不重复,这在一般情况下效率不高,但由于点的性质,这可能无关紧要。如果效率很重要,你可以编写自己的递归以您选择的语言运作。)

这种方法是有效的,因为它不能与超体积一起扩展,而只是与超曲面一起扩展,这是你想要枚举的(除非是非常大的半径,否则可能无关紧要:例如,可以大大节省一个因素)如果半径为100,则速度为100倍。