我的一个朋友在一次采访中被问到这个问题。
给出两棵二叉树,解释如何创建差异,以使如果您具有该差异以及其中的任何一棵树,您都应该能够生成另一棵二叉树。实现函数createDiff(Node tree1, Node tree 2)
返回该差异。
Tree 1
4
/ \
3 2
/ \ / \
5 8 10 22
Tree 2
1
\
4
/ \
11 12
如果为您提供树2和差异,则应该能够生成树1。
我的解决方案:
将两个二叉树都转换为数组,其中左子节点在2n+1
,右子节点在2n+2
,并用-1表示空节点。然后只需对数组进行逐元素减法即可创建差异。如果树的节点值为-1,则该解决方案将失败,我认为必须有一个更好,更整洁的解决方案,但我无法弄清楚。
答案 0 :(得分:0)
答案 1 :(得分:0)
有很多方法可以做到这一点。
我建议您将树变成(parent, child, direction)
的三元组排序数组。因此,从tree1开始:
4
/ \
3 2
/ \ / \
5 8 10 22
这很快变成:
(None, 4, None) # top
(4, 3, L)
(3, 5, L)
(3, 8, L)
(4, 2, R)
(2, 10, L)
(2, 22, R)
您想得到什么
(None, 4, None) # top
(2, 10, L)
(2, 22, R)
(3, 5, L)
(3, 8, L)
(4, 2, R)
(4, 3, L)
与另一个做相同的事情,然后比较它们。
给出一棵树和diff,您可以首先将树变成这种形式,查看diff,了解它的方向并使用patch获得所需的表示。然后,您可以递归地重建另一棵树。
之所以要使用这种表示,是因为如果两棵树共享任何共同的子树-即使它们在主树中的位置不同,它们也会共同出现。因此,如果树确实以某种有趣的方式匹配,则您可能会获得相对较小的差异。
编辑
对于@ruakh中的每个点,这确实假定值不会在树中重复。如果他们这样做,那么您可以这样做:
4
/ \
3 2
/ \ / \
5 8 10 22
成为
(, 4)
(0, 3)
(00, 5)
(01, 8)
(1, 2)
(10, 10)
(11, 22)
现在,如果您移动子树,它们将显示为较大的差异。但是,如果您只更改一个节点,那仍然是一个很小的差异。
答案 2 :(得分:0)
(问题(/ interview)中的示例在不显示任何大小无关的共享子结构方面不是很有帮助。或者在发起客户与开发人员之间的对话时突出显示了访谈问题。)
子树的重用需要一种表示法来识别。能够重建较小的树而不必走大部分差异似乎很有用。用固定的':
d e d--------e
c b "-" c b => C B' C' b
b a a b a a B a a
a a a
(问题陈述不是说 diff是线性的。)
注意事项:
如果我现在想像(或面试官建议)两棵大树,它们相同,但中间的某个节点具有不同的值,该怎么办?
好吧,至少它的子树将被共享,而“其他子树”将一直共享到根。如果树已退化并且几乎所有节点都是该路径的一部分,那就太糟糕了。
巨大的树木与孩子的根交换了吗?
(检测不止一次的树木有机会在这里闪耀。)
更大的问题似乎是“ diff”中表示的整个树,而要求可能是
给出一棵树, diff 将支持使用很少的空间和处理来重建另一棵树。
(它可能包括设置差异也应该很便宜-我会立即挑战: small diff 看起来与编辑距离。)
需要一种方法来识别每棵树中的“关键节点” -btilly's suggestion of "left-right-string"就像金子一样。
然后,需要一种方法来保持孩子与价值观的差异。
这是我期望在采访中达成的交流的终点。
要检测重用的树,我将 height 添加到每个内部节点。对于原理证明,我可能会在合适的序列化中使用查找重复字符串的现有实现。
答案 3 :(得分:0)
有很多方法可以考虑可行的差异结构。
一种幼稚的方法是将两棵树存储在一个元组中。然后,当您需要重新生成一棵树时,给定另一棵树和diff,则只需将给定树与diff的第一个元组条目中的树进行比较时寻找一个不同的节点。如果找到,则从第一个元组条目返回该树。如果找不到,则从diff元组中返回第二个。
面试官可能会要求减少内存消耗的替代方案。人们可能会想一想一种结构,当只有几个值或节点不同时,它的大小会很小。在两棵树都相等的极端情况下,这种差异也将是(近)空的。
在定义diff的结构之前,我先定义以下术语:
想象一下,树得到了额外的NIL叶节点,即一棵空树将由1个NIL节点组成。一棵只有一个根节点的树将有两个NIL节点作为其直接子节点,等等。
当通过根的相同路径(例如,左-左-右)到达节点时,这两个树都是公用的,而不管它们是否包含相同的值或具有相同的子代。当一个节点是一棵或两棵树(如上定义)中的NIL节点时,它甚至可以是公共节点。
公共节点(包括NIL节点,当它们是公共节点时)获得预序号(0、1、2,...)。在此编号过程中, 不常见的节点将被丢弃。
区别可能是元组列表,其中每个元组都具有以下信息:
仅当一个公共节点的值不同,并且两者中至少有一个是非NIL节点时,才将其添加到一个公共节点。
可以通过在树的公共节点上进行预排序来创建差异。
这是JavaScript的实现:
class Node {
constructor(value, left, right) {
this.value = value;
if (left) this.left = left;
if (right) this.right = right;
}
clone() {
return new Node(this.value, this.left ? this.left.clone() : undefined,
this.right ? this.right.clone() : undefined);
}
}
// Main functions:
function createDiff(tree1, tree2) {
let i = -1; // preorder sequence number
function recur(node1, node2) {
i++;
if (!node1 !== !node2) return [[i, (node1 || node2).clone()]];
if (!node1) return [];
const result = [];
if (node1.value !== node2.value) result.push([i, node1.value ^ node2.value]);
return result.concat(recur(node1.left, node2.left), recur(node1.right, node2.right));
}
return recur(tree1, tree2);
}
function applyDiff(tree, diff) {
let i = -1; // preorder sequence number
let j = 0; // index in diff array
function recur(node) {
i++;
let diffData = j >= diff.length || diff[j][0] !== i ? 0 : diff[j++][1];
if (diffData instanceof Node) return node ? undefined : diffData.clone();
return node && new Node(node.value ^ diffData, recur(node.left), recur(node.right));
}
return recur(tree);
}
// Create sample data:
let tree1 =
new Node(4,
new Node(3,
new Node(5), new Node(8)
),
new Node(2,
new Node(10), new Node(22)
)
);
let tree2 =
new Node(2,
undefined,
new Node(4,
new Node(11), new Node(12)
)
);
// Demo:
let diff = createDiff(tree1, tree2);
console.log("Diff:");
console.log(diff);
const restoreTree2 = applyDiff(tree1, diff);
console.log("Is restored second tree equal to original?");
console.log(JSON.stringify(tree2)===JSON.stringify(restoreTree2));
const restoreTree1 = applyDiff(tree2, diff);
console.log("Is restored first tree equal to original?");
console.log(JSON.stringify(tree1)===JSON.stringify(restoreTree1));
const noDiff = createDiff(tree1, tree1);
console.log("Diff for two equal trees:");
console.log(noDiff);