Какой самый быстрый способ найти самый глубокий путь в трехмерном массиве?
Я пытаюсь найти решение своей проблемы более недели, и я не мог найти ничего лучше, чем про итерация milion iterations, поэтому я думаю, что пришло время попросить кого-нибудь помочь мне.
У меня есть 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)
Обратите внимание, что даже если красная ячейка на втором этаже расположена рядом с зеленым, это не та же пещера, потому что она расположена по диагонали, и это не считается.
Мне сказали, что лучший способ сделать это может использовать рекурсивный алгоритм "делить и править", однако я действительно не знаю, как это могло бы выглядеть.
Ответы
Ответ 1
Думаю, вы сможете сделать это в O (N).
Когда вы анализируете свой ввод, назначьте каждый node a 'caveNumber' инициализирован равным 0. Установите его в действительное число всякий раз, когда вы посещаете пещеру:
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).
Вы будете посещать каждый node 7 раз в худшем случае: один раз из внешнего цикла и, возможно, один раз от каждого из своих шести соседей. Но вы будете работать только с каждым из них, так как после этого будет установлен caveNumber, и вы проигнорируете его.
Вы можете выполнить отслеживание глубины, добавив параметр глубины в рекурсивный вызов ProcessNode и увеличивая его только при посещении младшего соседа.
Ответ 2
Решение, показанное ниже (как программа python), запускается со временем O(n lg*(n))
, где lg*(n)
- это почти постоянная функция повторного логарифма, часто связанная с операциями объединения в лесах с непересекающимися множествами.
В первом проходе через все ячейки программа создает лес с непересекающимся множеством, используя подпрограммы, называемые makeset(), findset(), link(),
и union()
, как описано в разделе 22.3 (леса с разнесенными наборами) издания 1 Cormen/Leiserson/Ривест. В более поздних проходах через ячейки он подсчитывает количество членов каждого непересекающегося леса, проверяет глубину и т.д. Первый проход выполняется во времени 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 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
Ответ 3
Вы можете наблюдать это как график, где (недиагональные) смежные элементы связаны, если они оба пустые (часть пещеры). Обратите внимание, что вам не нужно преобразовывать его в график, вы можете использовать нормальное представление 3d-массива.
Поиск пещер - это та же задача, что и поиск подключенных компонентов на графике (O (N)), а размер пещеры - это количество узлов этого компонента.
Ответ 4
Похоже, вы решаете проблему с "связанными компонентами". Если ваш 3D-массив можно преобразовать в бит-массив (например, 0 = коренной корень, 1 = пещера или наоборот), вы можете применить технику, используемую при обработке изображений, чтобы найти число и размеры либо переднего плана, либо фона.
Обычно этот алгоритм применяется в 2D-изображениях, чтобы найти "подключенные компоненты" или "капли" одного цвета. Если возможно, найдите алгоритм "одного прохода":
http://en.wikipedia.org/wiki/Connected-component_labeling
Та же самая техника может быть применена к 3D-данным. Googling "подключенные компоненты 3D" приведут к ссылкам вроде этого:
http://www.ecse.rpi.edu/Homepages/wrf/pmwiki/pmwiki.php/Research/ConnectedComponents
Как только алгоритм завершит обработку вашего 3D-массива, вы получите список помеченных, связанных областей, и каждый регион будет списком вокселей (элементов тома, аналогичных пикселям изображения). Затем вы можете проанализировать каждую помеченную область, чтобы определить объем, близость к поверхности, высоту и т.д.
Реализация этих алгоритмов может быть немного сложной, и вы можете сначала попробовать выполнить 2D-реализацию. Думал, что это может быть не так эффективно, как вам нравится, вы можете создать алгоритм маркировки трехмерных подключаемых компонентов, применяя 2D-алгоритм итеративно к каждому слою, а затем перевязывая связанные области от верхнего слоя к нижнему слою:
- Для слоя 0 найдите все подключенные области, используя алгоритм 2D-подключенного компонента
- Для слоя 1 найдите все связанные области.
- Если какой-либо помеченный пиксель в слое 0 находится непосредственно над помеченным пикселем в слое 1, измените все метки в слое 1 на метку в слое 0.
- Примените эту технологию меток итеративно через стек, пока не достигнете слоя N.
Одним из важных соображений в маркировке связанных компонентов является то, как можно рассматривать области для подключения. В 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-соединение.
Для трехмерных данных вы можете рассматривать либо 6-соединение, либо 26-соединение: 6-подключение рассматривает только соседние пиксели, которые разделяют полную поверхность куба с центром воксела, а 26-подключение рассматривает каждый соседний пиксель вокруг центра вокселя. Вы отмечаете, что "по диагонали" не учитывается, поэтому достаточно 6-соединений.