从移动指针的树中交换两个随机选择的节点的角色的算法
Posted
技术标签:
【中文标题】从移动指针的树中交换两个随机选择的节点的角色的算法【英文标题】:Algorithm to exchange the roles of two randomly chosen nodes from a tree moving pointers 【发布时间】:2016-02-01 22:46:22 【问题描述】:我创建了一个算法,其目的应该是,给定 BST 中的两个节点 A 和 B,它通过简单地移动指针来切换两者的角色(或树中的位置)。在我对 BST 的表示中,我使用的是双链接连接(即 A.parent == B 和 (B.left == A) 或 (B.right == A))。我不确定它是否完全正确。我把算法分为两种情况。
A 和 B 直接相连(A 是 B 的父级或 B 是 A 的父级)
其他情况
对于前面的每个案例,我都创建了一个嵌套函数。首先,我想就算法的正确性征求您的意见,如果我能以某种方式改进它。代码如下:
def switch(self, x: BSTNode, y: BSTNode, search_first=False):
if not x:
raise ValueError("x cannot be None.")
if not y:
raise ValueError("y cannot be None.")
if x == y:
raise ValueError("x cannot be equal to y")
if search_first:
if not self.search(x.key) or not self.search(y.key):
raise LookupError("x or y not found.")
def switch_1(p, s):
"""Switches the roles of p and s,
where p (parent) is the direct parent of s (son)."""
assert s.parent == p
if s.is_left_child():
p.left = s.left
if s.left:
s.left.parent = p
s.left = p
s.right, p.right = p.right, s.right
if s.right:
s.right.parent = s
if p.right:
p.right.parent = p
else:
p.right = s.right
if s.right:
s.right.parent = p
s.right = p
s.left, p.left = p.left, s.left
if s.left:
s.left.parent = s
if p.left:
p.left.parent = p
if p.parent:
if p.is_left_child():
p.parent.left = s
else:
p.parent.right = s
else: # p is the root
self.root = s
s.parent = p.parent
p.parent = s
def switch_2(u, v):
"""u and v are nodes in the tree
that are not related by a parent-son
or a grandparent-son relantionships."""
if not u.parent:
self.root = v
if v.is_left_child():
v.parent.left = u
else:
v.parent.right = u
elif not v.parent:
self.root = u
if u.is_left_child():
u.parent.left = v
else:
u.parent.right = v
else: # neither u nor v is the root
if u.is_left_child():
if v.is_left_child():
v.parent.left, u.parent.left = u, v
else:
v.parent.right, u.parent.left = u, v
else:
if v.is_left_child():
v.parent.left, u.parent.right = u, v
else:
v.parent.right, u.parent.right = u, v
v.parent, u.parent = u.parent, v.parent
u.left, v.left = v.left, u.left
u.right, v.right = v.right, u.right
if u.left:
u.left.parent = u
if u.right:
u.right.parent = u
if v.left:
v.left.parent = v
if v.right:
v.right.parent = v
if x.parent == y:
switch_1(y, x)
elif y.parent == x:
switch_1(x, y)
else:
switch_2(x, y)
我真的需要switch
在所有情况下都可以工作,无论我们选择哪个节点x
或y
。我已经做了一些测试,它似乎可以工作,但我仍然不确定。
编辑
最后,如果它有帮助的话,这里你有我的 BST 的完整实现(我正在做的测试):
https://github.com/dossan/ands/blob/master/ands/ds/BST.py
EDIT 2(只是好奇)
@Rishav 评论道:
我不明白这个函数背后的意图..如果是交换BST中的两个节点,交换它们的数据而不是操作指针还不够吗?
我回答:
好吧,也许我应该多补充一点关于所有这些“怪物”功能背后的原因。我可以在我的 BST 中插入
BSTNode
对象或任何类似的对象。当用户决定插入任何可比较的对象时,创建BSTNode
的责任是我的,因此用户无权访问初始BSTNode
引用,除非他们搜索密钥。但是BSTNode
只会在插入键后返回,或者树中已经有另一个BSTNode
对象具有相同的键(或值),但后一种情况无关紧要。用户还可以在树中插入一个
BSTNode
对象,该对象具有初始(并且应该保持不变)键(或值)。尽管如此,如果我只是交换节点的值或键,用户将引用具有不同键的节点,而不是他插入的节点的键。当然,我想避免这种情况。
【问题讨论】:
你想用s.right, p.right = p.right, s.right
这行代码做什么?我不确定它是否真的像你想象的那样做,看看***.com/questions/11502268/…
如果我理解正确,您希望交换二叉搜索树中的两个节点,同时保留树的所有其余部分不变。你能解释一下做这种手术的原因吗? BST 具有很强的有序性;一旦你交换其中任意两个任意选择的节点,它就不再是 BST。因此,您可能无法进行另一次此类交换,因为self.search(x.key)
或self.search(y.key)
将无法找到现有节点!
@kyle 我正在交换s
和p
的右孩子的值,即s
的右孩子成为p
的右孩子,反之亦然。
您还想进行树旋转吗?因为您不能仅通过交换节点来确保 BST 保持平衡?见en.wikipedia.org/wiki/Tree_rotation
@CiaPan 我非常了解 BST 的属性。当我创建我的删除功能时,我需要一个“交换”(或“切换”)功能来交换一个节点和它的继任者。现在,我只是决定让该功能适用于所有交换节点的情况。前一个仅适用于该特定情况,但我希望有一个适用于所有情况的函数,因为我最终可以更改删除函数的实现等。
【参考方案1】:
您需要适当的单元测试。我推荐python-nose - 非常容易使用。
至于测试向量,我建议使用两个节点 a 和 b 的每个潜在组合:
对于 BST 树,您有 3 种类型的节点:
-
叶节点,
1-子节点,
2 个子节点。
结合以下附加情况:
-
a 是 root,或
a 是 b 的父级,
a 不是 b 的父级。
以及它们的组合(也在对称情况下)。
然后在交换之后,您需要检查所有涉及的节点,即: a,b,a 和 b 的孩子,a 和 b 的父母,如果一切按计划进行的话。
我会使用一棵包含所有类型节点的小树来做到这一点。 然后遍历所有可能的节点组合并交换节点并检查预期结果,然后再次交换以使树恢复到其原始状态。
[编辑]
如果您的问题是如何避免所有繁琐的工作。您可以考虑寻找一些完善的 BST 实现并将结果与您的函数进行比较。向量可以通过使用准备好的树并生成该树的所有可能的节点对来自动创建。
[/编辑]
至于对函数的不需要的输入。你需要发挥你的想象力,尽管在我看来你已经涵盖了大多数情况。除了 Austin Hastings 提到的至少一个输入节点不属于树的地方。
我发现了为我的一个私人项目编写的相同功能的旧版本,也许你会发现它很有用:
def swap( a, b ):
if a == b: return
if a is None or b is None: return
#if a not in self or b not in self: return
if b.parent == a:
a, b = b, a
#swap connections naively
a.parent, b.parent = b.parent, a.parent
a.left, b.left = b.left, a.left
a.right, b.right = b.right, a.right
if b.parent == b: #b was the p of a
b.parent = a
if a.parent is not None:
if a.parent.left == b: a.parent.left = a
else: a.parent.right = a
else:
self.root = a
if b.parent is not None:
if b.parent.left == a: b.parent.left = b
else: b.parent.right = b
else:
self.root = b
if a.right is not None: a.right.parent = a
if a.left is not None: a.left.parent = a
if b.right is not None: b.right.parent = b
if b.left is not None: b.left.parent = b
和性能优化版:
def swap_opt( a, b ):
if a == b: return
if a is None or b is None: return
#if a not in self or b not in self: return
if b.p == a:
a, b = b, a
#swap connections naively
a.p, b.p = b.p, a.p
a.l, b.l = b.l, a.l
a.r, b.r = b.r, a.r
if b.p == b: #b was the p of a
b.p = a
if a.l == a:
a.l = b
if a.r is not None: a.r.p = a
else:
a.r = b
if a.l is not None: a.l.p = a
if b.r is not None: b.r.p = b
if b.l is not None: b.l.p = b
if a.p is not None:
if a.p.l == b: a.p.l = a
else: a.p.r = a
else:
#set new root to a
pass
else:
if a.r is not None: a.r.p = a
if a.l is not None: a.l.p = a
if b.r is not None: b.r.p = b
if b.l is not None: b.l.p = b
if a.p is not None:
if a.p.l == b: a.p.l = a
else: a.p.r = a
else:
#set new root to a
pass
if b.p is not None:
if b.p.l == a: b.p.l = b
else: b.p.r = b
else:
#set new root to b
pass
我没有对这段代码进行适当的单元测试——它按我的预期工作。我对实现之间的性能差异更感兴趣。
swap_opt
处理相邻节点的速度要快一些,与swap
的紧凑实现相比,它的速度提高了大约 5%。 [EDIT2] 但这取决于用于测试和硬件的树 [/EDIT2]
【讨论】:
【参考方案2】:您的BST.py
定义class BST
。该类的成员有一个元素self.root
,它可以指向一个节点。 如图所示,您的代码没有考虑到这一点。
我相信你需要处理这些情况:
-
将根节点与其子节点之一交换。
用非子节点交换根节点。
将非根节点与其子节点之一交换。
用非子非根节点交换非根节点。
编辑:重新检查 switch_1 后,我认为您确实处理了所有情况。
此外,调用者可能会要求您将不是树成员的节点交换为成员节点。或者交换两个都不是当前树成员的节点。检测这些情况需要一些代码,但您可能可以使用dict
或set
来跟踪树成员资格。我不知道您是否想将“换入”视为有效操作。
在几个地方你使用==.
比较节点这是一个可以被覆盖的操作。您应该使用is
和is not
进行身份比较以及与None.
的比较
最后,请考虑 Pythonifying 你的 BST
课程。它是一个可变的可迭代容器,所以应该尽可能支持standard operations。
【讨论】:
我的代码将self.root
考虑在内(可能不完全正确,但确实需要)。仔细看看。
关于==
的使用,我确实在BaseNode
类中覆盖了the __hash__
函数,但没有覆盖__eq__
...如果你想你可以看看实现.一般来说,正如您所建议的,我总是使用is
或is not
与None
进行比较。关于你最后的建议,我可能会在我很确定所有操作都没有错误的情况下这样做。
无论如何,我不想覆盖__eq__
(以及所有类似的方法),因为在我的情况下,我不想认为具有相同密钥的两个节点必然相同,但只有如果它们实际上指向同一个对象。
啊!我在 switch_1 中错过了它。我的错。
如果您愿意,可以下载整个存储库并通过运行 ./BST.py
来尝试测试。您需要先安装 package。如果您阅读了 README.md
... >
以上是关于从移动指针的树中交换两个随机选择的节点的角色的算法的主要内容,如果未能解决你的问题,请参考以下文章