在3D阵列中找到最深路径的最快方法是什么?

时间:2012-10-19 16:09:33

标签: arrays algorithm 3d

我一直试图找到解决我问题的方法超过一周,而且我找不到比毫秒迭代更好的东西,所以我觉得是时候请别人来帮助我了。

我有一个3D阵列。让我们说,我们谈的是地面,第一层是一个表面。 另一层是地下的地板。我必须找到最深的路径长度,地下孤立的洞穴数量和最大洞穴的大小。

这是我的问题的可视化。

Input:
5 5 5 // x, y, z
xxxxx
oxxxx
xxxxx
xoxxo
ooxxx

xxxxx
xxoxx

and so...

Output:
5 // deepest path - starting from the surface
22 // size of the biggest cave
3 // number of izolated caves (red ones) (izolated - cave that doesn't reach the surface)

请注意,即使二楼的红色单元放在绿色的旁边,它也不是同一个洞穴,因为它是对角放置的,不计算在内。 我被告知最好的方法,可能是使用递归算法“划分和规则”,但我真的不知道它是怎么样的。

4 个答案:

答案 0 :(得分:4)

我认为你应该能够在O(N)中做到这一点。 解析输入时,为每个节点分配一个初始化为0的“caveNumber”。每当您访问一个洞穴时,将其设置为有效数字:

CaveCount = 0, IsolatedCaveCount=0
AllSizes = new Vector.
For each node, 
   ProcessNode(size:0,depth:0);

ProcessNode(size,depth):
   If node.isCave and !node.caveNumber 
       if (size==0) ++CaveCount
       if (size==0 and depth!=0) IsolatedCaveCount++
       node.caveNumber = CaveCount
       AllSizes[CaveCount]++
       For each neighbor of node, 
            if (goingDeeper) depth++
            ProcessNode(size+1, depth).

在最坏的情况下,您将访问每个节点7次:一次来自外部循环,可能一次来自六个邻居中的每一个。但是你只会在每一个上工作一次,因为之后设置了caveNumber,你忽略了它。

您可以通过向递归ProcessNode调用添加深度参数来执行深度跟踪,并且仅在访问较低邻居时将其递增。

答案 1 :(得分:2)

下面显示的解决方案(作为python程序)在时间O(n lg*(n))中运行,其中lg*(n)是几乎不变的迭代日志函数,通常与不相交集林中的联合运算相关联。

在第一次遍历所有单元格时,程序使用名为makeset(), findset(), link(),union()的例程创建一个不相交的林,就像第1版第22.3节(脱节集森林)中所述Cormen / Leiserson / Rivest。在稍后通过单元格时,它会计算每个不相交的林的成员数量,检查深度等。第一个通道在时间O(n lg*(n))中运行,后来通过时间O(n)但是通过简单的程序更改某些传递可以在O(c)O(b)中运行,用于c个洞穴,共有b个单元格。

请注意,下面显示的代码不受上一个答案中包含的错误的影响,前一个答案的伪代码包含行号 if (size==0 and depth!=0) IsolatedCaveCount++
该线的错误是与表面连接的洞穴可能有地下上升的分支,另一个答案将错误地添加到其孤立的洞穴中。

下面显示的代码产生以下输出:
Deepest: 5 Largest: 22 Isolated: 3
(请注意,图中显示的24的计数应为22,从4 + 9 + 9开始。)

v=[0b0000010000000000100111000,   # Cave map
   0b0000000100000110001100000,
   0b0000000000000001100111000,
   0b0000000000111001110111100,
   0b0000100000111001110111101]
nx, ny, nz = 5, 5, 5
inlay, ncells = (nx+1) * ny,  (nx+1) * ny * nz
masks = []
for r in range(ny):
    masks += [2**j for j in range(nx*ny)][nx*r:nx*r+nx] + [0]
p = [-1 for i in range(ncells)]  # parent links
r = [ 0 for i in range(ncells)]  # rank
c = [ 0 for i in range(ncells)]  # forest-size counts
d = [-1 for i in range(ncells)]  # depths

def makeset(x): # Ref: CLR 22.3, Disjoint-set forests
    p[x] = x
    r[x] = 0
def findset(x):
    if x != p[x]:
        p[x] = findset(p[x])
    return p[x]
def link(x,y):
    if r[x] > r[y]:
        p[y] = x
    else:
        p[x] = y
        if r[x] == r[y]:
            r[y] += 1
def union(x,y):
    link(findset(x), findset(y))

fa = 0                          # fa = floor above
bc = 0                          # bc = floor's base cell #
for f in v:                     # f = current-floor map
    cn = bc-1                   # cn = cell#
    ml = 0
    for m in masks:
        cn += 1
        if m & f:
            makeset(cn)
            if ml & f:
                union(cn, cn-1)
            mr = m>>nx
            if mr and mr & f:
                union(cn, cn-nx-1)
            if m & fa:
                union(cn, cn-inlay)
        ml = m
    bc += inlay
    fa = f

for i in range(inlay):
    findset(i)
    if p[i] > -1:
        d[p[i]] = 0
for i in range(ncells):
    if p[i] > -1:
        c[findset(i)] += 1
        if d[p[i]] > -1:
            d[p[i]] = max(d[p[i]], i//inlay)
isola = len([i for i in range(ncells) if c[i] > 0 and d[p[i]] < 0])
print "Deepest:", 1+max(d), "  Largest:", max(c), "  Isolated:", isola

答案 2 :(得分:1)

如果(非对角线)相邻元素都是空的(洞穴的一部分),您可以将其视为图形。请注意,您不必将其转换为图形,您可以使用普通的3d数组表示。

查找洞穴与在图表中找到connected components(O(N))的任务相同,洞穴的大小是该组件的节点数。

答案 3 :(得分:1)

听起来你正在解决“连接组件”问题。如果您的3D阵列可以转换为位数组(例如0 =基岩,1 =洞穴,反之亦然),那么您可以应用图像处理中使用的技术来查找前景或背景的数量和尺寸。

通常,此算法应用于2D图像以查找相同颜色的“连接组件”或“斑点”。如果可能,找到“单程”算法:

http://en.wikipedia.org/wiki/Connected-component_labeling

相同的技术可以应用于3D数据。谷歌搜索“连接组件3D”将产生这样的链接:

http://www.ecse.rpi.edu/Homepages/wrf/pmwiki/pmwiki.php/Research/ConnectedComponents

算法处理完3D阵列后,您将拥有一个标记的连接区域列表,每个区域将是一个体素列表(与图像像素类似的体积元素)。然后,您可以分析每个标记区域以确定体积,接近表面,高度等。

实现这些算法可能有点棘手,您可能希望首先尝试2D实现。认为它可能不如您所愿,您可以通过迭代地将2D算法应用于每个层,然后将连接区域从顶层重新标记到底层来创建3D连接组件标记算法:

  1. 对于第0层,使用2D连接分量算法
  2. 查找所有连接的区域
  3. 对于第1层,找到所有连接的区域。
  4. 如果第0层中的任何标记像素直接位于第1层中的标记像素上,请将第1层中的所有标签更改为第0层中的标签。
  5. 在堆栈中迭代地应用此标记技术,直到您到达第N层。
  6. 考虑连通组件标签的一个重要因素是人们如何考虑连接区域。在2D图像(或2D阵列)中,我们可以考虑相邻元素的“4连通”区域

    X 1 X
    1 C 1
    X 1 X
    

    其中“C”是中心元素,“1”表示被认为是连接的邻居,“X”是我们不认为连接的邻居。另一种选择是考虑“8个连接的邻居”:

    1 1 1
    1 C 1
    1 1 1
    

    即,与中心像素相邻的每个元素都被认为是连通的。起初这可能听起来像是更好的选择。在真实世界的2D图像数据中,棋盘图案的噪声或对角线单个噪声像素将被检测为连接区域,因此我们通常测试4连接。

    对于3D数据,您可以考虑6连接或26连接:6连接仅考虑与中心体素共享完整立方体面的相邻像素,并且26连通性考虑中心体素周围的每个相邻像素。你提到“对角放置”不算数,所以6连接就足够了。