排序(或未排序)二叉树遍历python

时间:2017-09-02 17:26:05

标签: python algorithm binary-tree

尝试遍历未排序的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)

1 个答案:

答案 0 :(得分:1)

问题

您编写的代码存在一些问题,这些代码存在错误的假设,但我不知道您是否故意制作它们。

  1. 在Python中,不返回最后执行的语句(例如,与Ruby不同)。在您的函数_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调用中发生什么)。因此,您不必仅在到达叶子时返回结果,或者当值完全匹配时,您必须在每一步将结果一直返回到顶部。
  2. 变量范围已关闭:您永远不会更新最短距离的值(或者更确切地说,不是您想要的方式)。从本质上讲,你正在做的是:

    def f1(dist):
        f2(dist)
        return dist
    
    def f2(dist):
        dist = 1
    
    print f1(10)
    
  3. 此代码打印10.更改distf1内的f2值仅会影响函数内的dist值。因此,如上所述在f2中更改此值不会影响f1中dist的值。在f1f2中,参数dist都有本地范围。这就是你在_closest中所做的事情。当您发现一个更接近您要查找的值的节点时,您会在当前函数调用中在本地更改distclose的值。但是,在函数的一次调用中更改它们不会将修改传播到上述调用的distclose的其他实例。

    修复代码

    所以,有两个修复方法是交织在一起的。首先,您需要使用dist的子调用结果更新close_closest的值。为此,您需要_closest的实例,始终返回一些内容。

    显然,您选择的返回格式是“搜索值”,“找到最接近的值”,“&#39;值与值之间的距离”。所以我们现在就这样做。

    总是返回一些东西很简单:只需在每个分支的末尾添加一个return语句。至于distclose的更新,它是直截了当的,以下模式将在最终函数中重复出现:

    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 Noneif node.lif node.v != None。最好是在代码中保持一致,并且只使用一种方法。惯用的方法是检查is None(及其对面is not None
    • 您使用的是Python2,它在其生命终结中处于领先地位。在某些社区中遵守Python2(主要是因为建立在其上的生态系统(例如信息安全))。但是,如果您可以在Python 2和Python 3之间选择新项目,那么您应该使用Python 3。

    这个答案的结尾,希望这有帮助。如果事情不明确或者我在路上犯了错误,请随时提出问题澄清。