尝试遍历未排序的BT以找到与给定输入值最接近的值。问题是,我似乎无法穿越整棵树,我只是被困在一边。如何遍历两个分支?我在Java中看到了一些例子,但我很难理解。我的方法似乎也过于冗长了?
输入= 5 在树中找到最接近的值
**更新: 我已根据下面的建议更改了代码,并且我认为能够遍历每个分支。但仍然没有回归我需要的东西。
# 3
# 0 4
# 2 8
class Node:
def __init__(self, val):
self.l = None
self.r = None
self.v = val
class Tree:
def __init__(self):
self.root = None
def getRoot(self):
return self.root
def add(self, val):
if(self.root == None):
self.root = Node(val)
else:
self._add(val, self.root)
def _add(self, val, node):
if(val < node.v):
if(node.l != None):
self._add(val, node.l)
else:
node.l = Node(val)
else:
if(node.r != None):
self._add(val, node.r)
else:
node.r = Node(val)
def closest(self,val):
if self.root!=None:
return self._closest(val,self.root)
else:
return
def _closest(self,val,node,close=None,dist=None):
if node.v!=None:
if val==node.v:
print 'Value Exists in Tree'
return val,node.v,0
else:
#print close
if dist is None:
dist = abs(val-node.v)
if node.l:
self._closest(val,node.l,node.v,dist)
if node.r:
self._closest(val,node.r,node.v,dist)
else:
if abs(val-node.v)<dist:
dist = abs(val-node.v)
close = node.v
print close,dist
if node.l:
self._closest(val,node.l,close,dist)
if node.r:
self._closest(val,node.r,close,dist)
if node.r is None and node.l is None:
return val,close,dist
tree = Tree()
tree.add(3)
tree.add(4)
tree.add(0)
tree.add(8)
tree.add(2)
tree.closest(5)
答案 0 :(得分:1)
您编写的代码存在一些问题,这些代码存在错误的假设,但我不知道您是否故意制作它们。
_closest
中,代码几乎总是在第一次调用时进入分支if dist is None
。它没有的唯一情况是树的根是无或者是您正在寻找的确切值。根据它输入的分支,在此循环内执行的最后一个语句将是self._closest(val, node.l, node.v, dist)
,self._closest(val, node.r, node.v, dist)
或dist = abs(val - node.v)
。因为永远不会返回任何内容,如果第一次调用_closest
通过该分支,则函数将返回None(无论嵌套_closest
调用中发生什么)。因此,您不必仅在到达叶子时返回结果,或者当值完全匹配时,您必须在每一步将结果一直返回到顶部。变量范围已关闭:您永远不会更新最短距离的值(或者更确切地说,不是您想要的方式)。从本质上讲,你正在做的是:
def f1(dist):
f2(dist)
return dist
def f2(dist):
dist = 1
print f1(10)
此代码打印10.更改dist
或f1
内的f2
值仅会影响函数内的dist值。因此,如上所述在f2中更改此值不会影响f1中dist
的值。在f1
和f2
中,参数dist
都有本地范围。这就是你在_closest
中所做的事情。当您发现一个更接近您要查找的值的节点时,您会在当前函数调用中在本地更改dist
和close
的值。但是,在函数的一次调用中更改它们不会将修改传播到上述调用的dist
和close
的其他实例。
所以,有两个修复方法是交织在一起的。首先,您需要使用dist
的子调用结果更新close
和_closest
的值。为此,您需要_closest
的实例,始终返回一些内容。
显然,您选择的返回格式是“搜索值”,“找到最接近的值”,“&#39;值与值之间的距离”。所以我们现在就这样做。
总是返回一些东西很简单:只需在每个分支的末尾添加一个return语句。至于dist
和close
的更新,它是直截了当的,以下模式将在最终函数中重复出现:
if node.l is not None:
_, close_l, dist_l = self._closest(val, node.l, node.v, dist)
if dist_l < dist:
dist = dist_l
close = close_l
最后,功能如下:
def closest(self, val):
if self.root is not None:
return self._closest(val, self.root)
else:
return val, None, None
def _closest(self, val, node, close=None, dist=None):
if node.v is not None:
if val == node.v:
print 'Value Exists in Tree'
return val, node.v, 0
else:
if dist is None:
dist = abs(val - node.v)
close = node.v # Close must be initialized just like dist here
if node.l is not None:
_, close_l, dist_l = self._closest(val, node.l, node.v, dist)
if dist_l < dist:
dist = dist_l
close = close_l
if node.r is not None:
_, close_r, dist_r = self._closest(val, node.r, node.v, dist)
if dist_r < dist:
dist = dist_r
close = close_r
return val, close, dist
else:
if abs(val - node.v) < dist:
dist = abs(val - node.v)
close = node.v
if node.l is not None:
_, close_l, dist_l = self._closest(val, node.l, close, dist)
if dist_l < dist:
dist = dist_l
close = close_l
if node.r is not None:
_, close_r, dist_r = self._closest(val, node.r, close, dist)
if dist_r < dist:
dist = dist_r
close = close_r
return val, close, dist
else:
return val, close, dist
此代码有效并产生正确的结果。然而,它过于复杂,难以理解。事实上,我们可以做得更好,我们马上就能看到它。
首先,我们有四个返回语句,它们都做同样的事情:返回val,close,dist。我们可以简单地在函数的末尾返回,而不是在每个分支的末尾返回,它将具有相同的效果。我们可以改变:
def _closest(self, val, node, close=None, dist=None):
if node.v is not None:
if val == node.v:
return val, node.v, 0
else:
if dist is None:
# ...
return val, close, dist
else:
# ...
return val, close, dist
else:
return val, close, dist
要:
def _closest(self, val, node, close=None, dist=None):
if node.v is not None:
if val == node.v:
close, dist = node.v, 0
else:
if dist is None:
# ...
else:
# ...
return val, close, dist
其次,我们可以删除一个大分支以删除代码重复。目前,我们拥有的是:
if dist is None:
dist = abs(val - node.v)
close = node.v
# Compute _closest for right and left node, update dist and close accordingly
else:
if abs(val - node.v) < dist:
dist = abs(val - node.v)
close = node.v
# Compute _closest for right and left node, update dist and close accordingly
这可以简单地改写为:
if dist is None or abs(val - node.v) < dist:
dist = abs(val - node.v)
close = node.v
# Compute _closest for right and left node, update dist and close accordingly
目前我们有:
def closest(self, val):
if self.root is not None:
return self._closest(val, self.root)
else:
return val, None, None
def _closest(self, val, node, close=None, dist=None):
if node.v is not None:
if val == node.v:
close, dist = node.v, 0
else:
if dist is None or abs(val - node.v) < dist:
dist = abs(val - node.v)
close = node.v
if node.l is not None:
_, close_l, dist_l = self._closest(val, node.l, close, dist)
if dist_l < dist:
dist = dist_l
close = close_l
if node.r is not None:
_, close_r, dist_r = self._closest(val, node.r, close, dist)
if dist_r < dist:
dist = dist_r
close = close_r
return val, close, dist
因此,这比第一个版本好得多:更短,更易于阅读,更易于理解,更少重复的代码。这已经很好了。我们可以做得更好吗?是的,如果我们对我们遍历树的方式稍作修改。
目前,算法的大部分是:
出现的是除根之外的每个节点都将被研究两次:第一次将它作为当前节点的子节点进行研究,下一节将作为当前节点进行研究。 (请注意,即使它们被研究了两次,该函数也最多可以遍历每个节点一次)。因此,要做的修改是仅在节点是当前节点的情况下研究节点,而不关心孩子。不幸的是,这只有在我们更改_closest
函数的返回值时才有可能。我们将不是返回三个数字的元组,而是跨越调用共享字典:如果研究的节点比另一个节点更近,则每次迭代都将更新字典。由于计算状态保持在共享状态(字典),因此不需要通过return语句传递当前最近点和距离。这是代码:
def closest(self, val):
best = {"close": None, "dist": None}
if self.root is not None:
self._closest(val, self.root, best)
return val, best["close"], best["dist"]
def _closest(self, val, node, best):
if node is not None and node.v is not None:
if best["dist"] is None or abs(val - node.v) < best["dist"]:
best["dist"] = abs(val - node.v)
best["close"] = node.v
self._closest(val, node.l, best)
self._closest(val, node.r, best)
此方法类似于C语言中的方法:由于返回多个值不是一个选项,因此使用容器结构将多个结果包装回来。
我对这个问题的最佳意思是最短的解决方案,也是最容易阅读和理解的解决方案。我相信这是最后一个的情况(虽然它可能只是个人的个人偏好)。然而,共享的状态并不是每个人的品味。
最后,关于您首先提出的代码的几点:
if dist is None
,if node.l
,if node.v != None
。最好是在代码中保持一致,并且只使用一种方法。惯用的方法是检查is None
(及其对面is not None
)这个答案的结尾,希望这有帮助。如果事情不明确或者我在路上犯了错误,请随时提出问题澄清。