我们必须将二叉树的节点写入文件。什么是编写二叉树最节省空间的方法。我们可以将其存储在数组格式中,父级位于i
,其子级位于2i
,2i+1
。但是在稀疏二叉树的情况下,这将浪费大量空间。
答案 0 :(得分:38)
我喜欢的一种方法是存储preorder遍历,但也包含'null'节点。存储'null'节点不需要存储树的顺序。
这种方法的一些优点
例如,假设您有一个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-order
和pre/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], 0
(15₁₀≡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));