二叉树的高效阵列存储

时间:2010-04-20 14:07:48

标签: arrays algorithm data-structures binary-tree

我们必须将二叉树的节点写入文件。什么是编写二叉树最节省空间的方法。我们可以将其存储在数组格式中,父级位于i,其子级位于2i2i+1。但是在稀疏二叉树的情况下,这将浪费大量空间。

6 个答案:

答案 0 :(得分:38)

我喜欢的一种方法是存储preorder遍历,但也包含'null'节点。存储'null'节点不需要存储树的顺序。

这种方法的一些优点

  • 在大多数实际情况下,您可以比pre / post + inorder方法做得更好。
  • 序列化只需要一次遍历
  • 反序列化可以一次完成。
  • 可以在一次传递中获得inorder遍历而不构建树,如果情况需要它可能是有用的。

例如,假设您有一个64位整数的二叉树,您可以在每个节点之后存储一个额外的位,说明下一个节点是否为空节点(第一个节点始终是根节点)。空节点,您可以用一个位表示。

因此,如果有n个节点,则空间使用量为8n字节+ n-1个指示符比特+ n + 1比特用于空节点= 66 * n比特。

在pre / post + inorder中,你将最终使用16n个字节= 128 * n位。

所以你在这个pre / post + inorder方法中保存了62 * n位的空间。

考虑树

       100
      /   \
     /     \
    /       \
   10       200
  / \       /  \
 .   .     150  300
          / \    / \
         .   .   .  .

其中'。'是空节点。

您将序列化为100 10 . . 200 150 . . 300 . .

现在每个(包括子树)'preorder遍历为null'具有空节点数=节点数+ 1的属性。

这允许您在一次传递中给定序列化版本来创建树,因为第一个节点是树的根。接下来的节点是左子树,后面是右边,可以看作是这样的:

100 (10 . .) (200 (150 . .) (300 . .))

要创建inorder遍历,您可以在看到节点时使用堆栈并按下,并在看到null时弹出(在列表中)。结果列表是inorder遍历(可以在此处找到详细解释:C++/C/Java: Anagrams - from original string to target;)。

答案 1 :(得分:4)

如果你有一个(几乎)完整的树,那么2i,2i + 1(二元堆)方法确实是最好的方法。

否则,您将无法转义为每个节点存储ParentId(父索引)。

答案 2 :(得分:4)

考虑XML。这是一种树序列化。例如:

<node id="1">
    <node id="2">                                   1
    </node>                                       /   \
    <node id="3">                                2     3
        <node id="4">                                 / \
        </node>                                      4   5
        <node id="5">
        </node>
    </node>
</node>

然后,为什么空格和标签?我们可以一步一步地省略它们:

<1>
   <2></>
   <3>
     <4></>
     <5></>
   </>
</>

删除空格:<1><2></2><3><4></><5></></></>

删除尖括号:12/34/5///

现在的问题是:如果一个节点有一个空的左子树和一个非空的右子树怎么办? 然后我们可以使用另一个特殊字符“#”来表示一个空的左子树。

例如:

    1
  /   \
      2
     /  \
    3

此树可以序列化为:1#23///

答案 3 :(得分:1)

您可以在文件中保存二叉树的in-orderpre/post-order遍历,并从这些遍历中重建树。

答案 4 :(得分:0)

由于只能存储2ⁿ-1个项目,而不会浪费任何空间,因此您必须将N个元素拆分为子树,顺序存储,并在树之间保留值的“主索引”以决定在哪个位置要搜索的树。一种明显的决定子树的方法是利用数字的二进制格式,这里我说的是N,即树中的项目数。 / p>

假设您有20个不同项目的排序序列:

['aeu', 'bfz', 'cdi', 'dfc', 'eap', 'ggk', 'gsb', 'guj', 'idm', 'ieg',
 'izr', 'pba', 'plp', 'rap', 'rhp', 'tat', 'uho', 'uwb', 'wdf', 'yhp']

20₁₀是10100²,并且1位决定拆分。

主表中的第一个条目将为item[15], 015₁₀≡01111_2,小于2的最大乘方小于或等于N, 0 到目前为止,您的输出列表的长度,又名子树的起始索引)。再次利用二进制算术,前15个(0–14)项将附加到输出列表中:item [7₁₀≡0111²]为0,item [3₁₀≡0011²]为1,item [11₁₀≡1001²]为2等等,因此保留了item[i] < item[2*i] && item[i] > item[2*i+1]的规则。

第一步之后:

master_table = [('tat', 0)]
output_list = ['guj', 'dfc', 'pba', 'bfz', 'ggk', 'ieg', 'rap', 'aeu', 'cdi', 'eap', 'gsb', 'idm', 'izr', 'plp', 'rhp']

如果N ==2ⁿ,则此python生成器将产生所需的顺序:

def heap_order(pow2):
    """Return the tree order of items
    pow2 MUST be a power of two"""

    bit= pow2>>1
    while bit:
        yield from range(bit - 1, pow2, bit*2)
        bit>>= 1

删除(或忽略)列表中的前16个项目,并重复列表长度的下一个1位:

剩余的输入列表为['uho', 'uwb', 'wdf', 'yhp'],其长度为4₁₀== 100 2。最大的2ⁿ-1是3,因此您将基于零的项3以及输出列表的当前长度附加到主表中,然后将前3个项附加到输出列表中,结果是:

master_table = [('tat', 0), ('yhp', 15)]
# master table items: (key, subtree_base_index)
output_list = [
    'guj', 'dfc', 'pba', 'bfz', 'ggk',
    'ieg', 'rap', 'aeu', 'cdi', 'eap',
    'gsb', 'idm', 'izr', 'plp', 'rhp',

    'uwb', 'uho', 'wdf'
]

整个结果等效于二叉树,其中所有左子树都有 叶节点带有两个孩子的树节点。

要搜索值,请执行以下操作:扫描主表以查找大于搜索键的键,并始终在index - subtree_base_index,{{ 1}}计算。失败搜索所需的比较的最大总总数(主表+子树)通常为⌈log_2N⌉,有时-当搜索键大于主表的第一个条目时-小于(没有为N的0位创建子树)。

您还应该将每个子树的级别计数存储在主表中(这样您就可以知道在搜索了几次之后您应该停止搜索子树)。

答案 5 :(得分:0)

您可以存储级别顺序,也就是 DFS 输出,包括任何空子级。从值和空值列表重建树时,使用节点队列,知道列表中的下两个项目始终是队列中当前节点的左侧和右侧。

这仍然需要存储总共 n+1 个“空符号”,但反序列化是迭代完成的,而不是在预序的情况下递归完成。

JS 中的 POC:

class Node {
  constructor(val, left=null, right=null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

function dfs(root) {
  const queue = [root];
  const output = [];

  while (queue.length > 0) {
    const curr = queue.pop();

    // if curr is null, push null, else curr.val
    output.push(curr && curr.val);

    if (curr !== null) {
      queue.unshift(curr.left);
      queue.unshift(curr.right);
    }
  }

  return output;
}

function undfs(list) {
  const root = new Node(list.shift());
  const queue = [root];

  while (queue.length > 0) {
    const curr = queue.shift();
    const [leftVal, rightVal] = list.splice(0,2);

    if (leftVal) {
      curr.left = new Node(leftVal);
      queue.push(curr.left);
    }

    if (rightVal) {
      curr.right = new Node(rightVal);
      queue.push(curr.right);
    }
  }

  return root;
}

const root =
  new Node(100,
    new Node(10),
    new Node(200,
      new Node(150),
      new Node(300)
    )
  );

const dfsOutput = dfs(root);

console.log("List output:");
console.log(dfsOutput.map(a => a || "null").join(", "));

console.log("\nTree output:");
console.log(undfs(dfsOutput));