在完整树的深度优先和广度优先遍历之间转换的函数
Posted
技术标签:
【中文标题】在完整树的深度优先和广度优先遍历之间转换的函数【英文标题】:Functions to convert between depth first and breadth first traversals of a complete tree 【发布时间】:2016-08-22 01:59:35 【问题描述】:问题:考虑一个具有 l 层的完整 k-ary 树,节点在广度优先遍历中由它们的等级标记。按照深度优先遍历的顺序计算标签列表。
例如,对于具有 3 层的二叉树,所需的列表是: [0 1 3 7 8 4 9 10 2 5 11 12 6 13 14]
一种方法是实际构造一个树结构并遍历它两次;第一次遍历是广度优先,边走边标记节点 0,1,2,..。第二次遍历是深度优先,在你走的时候报告标签 0,1,3,7,..。
我对避免在内存中构造树的方法感兴趣。我意识到这种树的大小与输出的大小相当,但我希望该解决方案将允许“流式”输出(即不需要完全存储在内存中的输出)。
我也对伴生问题感兴趣;从根据深度优先遍历标记的树开始,并生成广度优先遍历的标签。我想这个问题的解决方案在某种意义上与第一个问题是双重的。
【问题讨论】:
我们假设这是一棵完整的树吗? 一棵完整的树与一棵完整的树有什么不同吗? 我想我还是误用了这个词,我的意思是问我们是否可以假设在 k=2, l=3 树中恰好有 15 个节点(不少于) 【参考方案1】:您实际上并不需要构建树。您可以仅使用 BFS 标签而不是指向实际节点的指针进行深度优先遍历。
使用 BFS 位置标签来表示 k-ary 树中的节点:
根是0
任何节点n
的第一个子节点是k*n + 1
节点n
的右兄弟,如果有的话,是n+1
在代码中是这样的:
class Whatever
static void addSubtree(List<Integer> list, int node, int k, int levelsleft)
if (levelsleft < 1)
return;
list.add(node);
for (int i=0; i<k; i++)
addSubtree(list, node*k+i+1, k, levelsleft-1);
public static void main (String[] args) throws java.lang.Exception
int K = 2, LEVELS = 4;
ArrayList<Integer> list = new ArrayList<>();
addSubtree(list, 0, K, LEVELS);
System.out.println(list);
这其实一直是用来表示数组中的二叉堆——节点是BFS顺序的数组元素,通过对索引执行这些操作来遍历树。
【讨论】:
这看起来正是我想要的。也很优雅。 “伴侣”功能会是什么样子? @sitiposit,我可以想出一个方法去另一个方向,但它并不那么漂亮(它有一个内部循环),据我所知并没有在现实生活中实际使用.我认为在上面的答案中坚持这样的事情会毁了它:)【参考方案2】:您可以使用标准的 DFS 和 BFS 算法,但是您可以根据需要计算它们,而不是从预先构建的树结构中获取特定节点的子节点。
对于一个 BFS 编号、高度为 H 的完整 K-ary 树,节点的第 i 个子节点 N 在深度 D 是:
K*N + 1 + i
当提供i = 0
(第一个孩子)here 时,此公式的推导。
对于一个 DFS 编号、高度为 H 的完整 K-ary 树,节点的第 i 个子节点 N 深度 D 由一个更丑陋的公式给出:
N + 1 + i*step where step = (K^(H - D) - 1) / (K - 1)
下面是这个公式的粗略解释:
对于深度为 D 的节点 N 在高度为 H 的 DFS 编号 K-ary 树中,它的第一个子节点只是 N+1 因为它是深度优先遍历中要访问的下一个节点。 N 的第二个孩子将在访问以第一个孩子为根的整个子树(N+1)之后直接被访问,它本身就是一个完整的K -高度树
H - (D + 1)
。任何完整的K-ary 树的大小由有限几何级数的总和给出,如here 所述。所述子树的大小是第一个和第二个孩子之间的距离,实际上,它是所有兄弟姐妹之间的相同距离,因为它们的每个子树都是相同的大小。如果我们称这个距离为step
,那么:第一个孩子是
N + 1
第二个孩子是N + 1 + step
第三个孩子是N + 1 + step + step
...等等。
下面是一个 Python 实现(注意:dfs
函数使用 BFS 公式,因为它正在从 DFS 转换为 BFS,反之亦然 bfs
函数。):
def dfs(K, H):
stack = list()
push, pop = list.append, list.pop
push(stack, (0, 0))
while stack:
label, depth = pop(stack)
yield label
if depth + 1 > H: # leaf node
continue
for i in reversed(range(K)):
push(stack, (K*label + 1 + i, depth + 1))
def bfs(K, H):
from collections import deque
queue = deque()
push, pop = deque.append, deque.popleft
push(queue, (0, 0))
while queue:
label, depth = pop(queue)
yield label
if depth + 1 > H: # leaf node
continue
step = (K**(H - depth) - 1) // (K - 1)
for i in range(K):
push(queue, (label + 1 + i*step, depth + 1))
print(list(dfs(2, 3)))
print(list(bfs(2, 3)))
print(list(dfs(3, 2)))
print(list(bfs(3, 2)))
上面将打印:
[0, 1, 3, 7, 8, 4, 9, 10, 2, 5, 11, 12, 6, 13, 14]
[0, 1, 8, 2, 5, 9, 12, 3, 4, 6, 7, 10, 11, 13, 14]
[0, 1, 4, 5, 6, 2, 7, 8, 9, 3, 10, 11, 12]
[0, 1, 5, 9, 2, 3, 4, 6, 7, 8, 10, 11, 12]
【讨论】:
这正是我正在寻找的解决方案。很好地说明了二元性。我当然相信第 4 行和第 7 行的公式,但我发现很难理解它们为什么起作用。您能否提供任何理由或指出相关参考资料。 老实说,我只是手工勾勒出了 (K, D) = (2, 3), (3, 2), (3, 3) 的深度优先和广度优先的例子将我的结果外推到一般公式中。我不确定我能否提出一个很好的直观或严格的解释。我将为此打开一个单独的问题。 您在上面粉红色中包含的解释很有帮助。我现在对公式更有信心了。【参考方案3】:这里有一些似乎可以解决问题的 javascript。
var arity = 2;
var depth = 3;
function look(rowstart, pos, dep)
var number = rowstart + pos;
console.log(number);
if (dep < depth-1)
var rowlen = Math.pow(arity, dep);
var newRowstart = rowstart + rowlen;
for (var i = 0; i < arity; i++)
look(newRowstart, pos*arity + i, dep+1);
look(0, 0, 0);
这是一种深度优先搜索,计算每个节点在下行过程中的 BFS 标签。
它使用当前深度dep
、当前行中的水平位置(pos
)和行中第一个节点的标签(rowstart
)计算节点的标签。
【讨论】:
以上是关于在完整树的深度优先和广度优先遍历之间转换的函数的主要内容,如果未能解决你的问题,请参考以下文章