如何从深度优先遍历索引计算完美二叉树中节点的层级?

7
我有一颗完美的二叉树,即每个节点都是叶子节点或者有两个子节点,并且所有叶子结点都在同一层级。每个节点在深度优先顺序下有一个索引。
(例如,在具有3级的树中,根节点的索引为0,第一个子节点为1,第一个子节点的第一个子节点为2,第一个子节点的第二个子节点为3,第二个子节点为4,第二个子节点的第一个子节点为5,第二个子节点的第二个子节点的索引为6。)
      0
    /   \
  1      4
 / \    / \
2   3  5   6

我知道树的大小(节点数/最大层数),但只知道特定节点的索引,我需要计算它的层数(即到根节点的距离)。如何以最有效的方式完成这项工作?


如果一个节点可以有>2个子节点,那么它就不是二叉树。 - Justin
请阅读问题:“这是深度优先,但不是完美的二叉树” - Viktor Lova
1
您还需要知道节点的总数,否则可能无法计算出层级。 - Sergey Kalinichenko
@nsinreal 嗯,这个问题有误导性。它说“我有一棵完美的二叉树”和“这是深度优先,但不是一棵完美的二叉树”。 - Justin
@Justin 好的,那只是深度优先顺序的一个例子。 - Viktor Lova
是的,我应该更清楚地表述。 - hrehfeld
7个回答

12

这是另一个能够让解决此问题更为简单的建议:

如果您按照广度优先的顺序为节点标记索引,您可以在O(1)时间内计算出其所处的层数,而无需进行遍历。因此,如果您需要进行多个查询,您可以进行一次O(N)的广度优先遍历,并在O(1)的时间内回答每个查询。

计算层数的公式如下:

level = floor(log(index + 1))

以2为底的对数在哪里

在这棵树上试一试:

       0
     /    \
    /      \
   1        2
  / \      / \
 /   \    /   \
3     4  5     6

干杯。


抱歉,但是布局要求按照深度优先顺序进行。 - hrehfeld

6

i 成为您要查找的索引,n 成为节点的总数。

这个算法可以实现您想要的功能:

level = 0
while i != 0 do
    i--
    n = (n-1)/2
    i = i%n
    level++
done

0是根的索引,如果i=0那么你在正确的层级,否则你可以移除根节点并得到两个子树。n=(n-1)/2更新了新树(它是旧树的子树)中节点的数量,i=i%n只选择正确的子树。


我刚刚添加了一些解释。 - Thomash
嗯,现在我明白它是如何工作的了,但是我觉得短语“i = i%n仅选择好的子树”是错误的。看起来你是“替换坐标”以使算法在子树中工作,其中根= 0。顺便说一句,很棒的算法。 - Viktor Lova

3
似乎直接在树上行走应该足够有效。算法的每一步中,记得牢记你所在节点子树索引的范围。范围的第一个值是根节点,之后的前半部分是左子树的范围,后半部分应该是右子树的范围。然后可以递归向下移动,直到找到目标节点。例如,在具有15个元素的4级树中搜索。
                 (root node)(left elements)(right elements)
Starting range:  (0)(1 2 3 4 5 6 7)(8 9 10 11 12 13 14)
Go left       :  (1)(2 3 4)(5 6 7)
Go right      :  (5)(6)(7)
Found node, total depth 2

您应该可以用简单的循环完成此操作,只需使用几个变量来存储范围的开始和结束位置。如果您进行一些小的更改,例如使用后序/前序/中序遍历或从1而不是0开始索引,则也很容易适应此操作。


这个看起来也是正确的,并且非常适合一个易于维护的解决方案,但可能不符合我的性能要求。 - hrehfeld
@hrehfeld:实际上,它应该符合性能要求。你从中得到的任何算法都应该与Tomash等人提出的非常相似。 - hugomg

2

未经测试:

int LevelFromIndex( int index, int count)
{
    if (index == 0)
        return 0;
    if (index > (count - 1)/ 2)
        index -= (count - 1) / 2;
    return 1 + LevelFromIndex( index - 1, (count - 1) / 2);
}

这里的count是树中节点的总数。


(count + 1) / 2 - 1 看起来有点奇怪,为什么不用 (count - 1) / 2 呢? - Thomash

0

如果你只有索引,你无法找到深度。

假设你有这样一棵树:

    1
   / \
  2   5
 / \
3   4

索引为3的节点深度为2。

假设您有这样一棵树:

  1
 / \
2   3
   / \
  4   5

索引为3的节点深度为1。

仅凭索引无法区分这两棵树。仅凭索引无法找到距离根节点的距离。

编辑:如果您指的是完美二叉树,即所有叶子节点深度相同,每个父节点都有两个子节点,则仍然无法找到深度。

比较这两棵树:

  1
 / \
2   3


      1
   /     \
  2       5
 / \     / \
3   4   6   7

第三个节点的深度取决于树的高度。

编辑2:如果您知道整棵树的高度,可以使用这个递归算法:

def distanceFromRoot(index, rootIndex, treeHeight):
    if index == rootIndex:
        return 0
    leftIndex = rootIndex+1
    rightIndex = rootIndex + 2**treeHeight
    if index >= rightIndex:
        return 1 + distanceFromRoot(index, rightIndex, treeHeight-1)
    else:
        return 1 + distanceFromRoot(index, leftIndex, treeHeight-1)

我的例子树不符合这个标准吗?所有节点都有0或2个子节点。 - Kevin
我认为“完美”的树通常指的是“完全填充”的树。也就是说,如果你有k层,则有2^k - 1个节点。 - hugomg
是的,但我们知道树的大小。 - Viktor Lova
啊,问题定义一直在变化 :) - Kevin
抱歉!我以为我的问题很明确,但实际上并不是 :-) - hrehfeld
显示剩余2条评论

0

所以,我们有一个四层的树结构:

          0             - 0th level
      /       \         
     1          8       - 1th level
   /  \       /  \      
  2    5     9    12    - 2th level
 / \   /\   / \   / \
3   4 6  7 10 11 13 14  - 3th level

如您所见,每个左子节点的根索引都加了一(root+1),因为在DFS中,左子节点总是首先被访问。 第二个节点的左节点索引增加了左子树大小的数量(right=left+leftSize)。如果我们知道树的深度,我们就可以计算它的大小(size=2^depth-1)。由于左子树的深度等于父级深度减去1,所以它的大小为2^(parentDepth - 1) - 1。

现在我们有了一个算法——计算左节点的索引,计算右节点的索引。如果节点索引位于它们之间,则转到左节点;否则,转到右节点。

代码如下:

static int level(int index, int root, int treeDepth) {
        if (index == root)
            return 0;

        if (treeDepth <= 0 /* no tree */ || treeDepth == 1 /* tree contains only root */)
            throw new Exception("Unable to find node");

        int left = root + 1;
        int right = left + (int)Math.Pow(2, treeDepth - 1) - 1;

        if (index == left || index == right)
            return 1;

        if (left < index && index < right)
            return 1 + level(index, left, treeDepth - 1);
        else
            return 1 + level(index, right, treeDepth - 1);
    }

P.S:对我的英语表示歉意,请。 - Viktor Lova

0

编辑:第一次尝试...仅适用于BFS。

如果您所说的完美二叉树是指具有堆结构的二叉树,则可以使用以下公式计算节点的父索引:

parentIndex = (index-1)/2

所以你可以重复使用该公式,直到达到<=0,每次循环实际上都会向树中的上一级移动。

编辑:尝试第二次...

我能想到的最好方法需要O(索引+ log n),即O(n)的时间复杂度。进行深度优先搜索,直到达到所需的索引,然后使用父指针继续沿着树向上走,直到达到根节点,并跟踪你向上走的次数。这假定每个节点都有一个父指针。


1
不是因为他使用深度优先顺序的索引。 - Thomash
@Justin "O(index + log n) which is still O(log n)" -> 没有O(index + log n)是O(n)。 - Thomash
@Thomash 哎呀!又发现问题了! - Justin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接