如何在二叉搜索树中以恒定时间获得高度?

时间:2017-06-28 08:29:14

标签: python recursion binary-search-tree

所以我有一个二叉搜索树,我试图获得所述树的高度。我有一个self.height属性,每当我执行insert_element(self,value)时递增,并在remove_element(self,value)发生时递减。但是,我注意到,每当其中一种方法出现时,这会递增和递减,并且如果节点处于不会改变高度的相同高度,则不会考虑这种情况。

class Binary_Search_Tree:

    class __BST_Node:

        def __init__(self, value):
            self.value = value
            self.lchild = None
            self.rchild = None

    def __init__(self):
        self.__root = None
        self.height = 0

    def insert_element(self, value):
        self._insert(self.__root, value)

    def _insert(self, root, value):
        node = Binary_Search_Tree.__BST_Node(value)
        if root == value:
            raise ValueError("Value already exists")
        if self.__root is None:
            self.__root = Binary_Search_Tree.__BST_Node(value)
            self.height += 1
            return self.__root
        else:
            if root.value >  node.value:
                if root.lchild is None:
                    root.lchild = node 
                    self.height += 1
                    return root.lchild 
                else:
                    self._insert(root.lchild, value)
            elif root.value < node.value:
                if root.rchild is None:
                    root.rchild = node
                    self.height += 1
                    return root.rchild
                else:
                    self._insert(root.rchild, value)
        return root

    def remove_element(self, value):
        self.__root = self._remove(self.__root, value)
        self.height -= 1
        return self.__root

    def _remove(self, root, value):
        if self.__root is None:
            raise ValueError("Tree is empty")
        if root.value != value:
            raise ValueError("No such value")
        if root.value == value:
            if root.lchild is None and root.rchild is None:
                root = None
                self.height -= 1
                return root
            elif root.lchild is None or root.rchild is None:
                if root.lchild is None:
                    self.height -= 1
                    return root.rchild
                if root.rchild is None:
                    self.height -= 1
                    return root.lchild
            elif root.lchild and root.rchild:
                parent = root
                successor = root.rchild
                while successor.lchild:
                    parent = successor
                    successor = successor.lchild
                root.value = successor.value
                if parent.lchild == successor:
                    parent.lchild = successor.rchild
                    self.height -= 1
                    return parent.lchild
                else:
                    parent.rchild = successor.rchild
                    self.height -= 1
                    return parent.rchild
        else:
            if root.value > value:
                if root.lchild:
                root.lchild = self._remove(root.lchild, value)
        elif root.value < value:
            if root.rchild:
                root.rchild = self._remove(root.rchild, value)
        return root

    def in_order(self):
        if self.__root is not None:
            self._in(self.__root)

    def _in(self, root):
        if root is None:
            return
        if root is not None:
            self._in(root.lchild)
            print(str(root.value)) 
            self._in(root.rchild)

    def get_height(self):
        print(str(self.height))

    def __str__(self):
        return self.in_order()


if __name__ == '__main__':
    pass

1 个答案:

答案 0 :(得分:0)

以下是您应该完成工作的树的一个版本。 我试图让它与原始代码保持尽可能相似但不得不稍微改变一下,同时试图找出如何使示例工作。

希望它仍然足够接近,让您能够识别关键部件并根据需要重新使用它。

class Binary_Search_Tree:

    class __BST_Node:

        def __init__(self, value, height):
            self.value = value
            self.height = height
            self.lchild = None
            self.rchild = None

    def __init__(self):
        self.__root = None
        self.__height = 0

    def _insert(self, knot, value):
        if knot is None:
            self.__root = Binary_Search_Tree.__BST_Node(value, 1)
            result = self.__root
        elif knot.value == value:
            # replace error with WARNING, to handle exception
            # raise ValueError("Value already exists")
            print 'WARNING: value', value, 'already exists; skipped'
            return knot
        elif knot.value > value:
            if knot.lchild is None:
                knot.lchild = Binary_Search_Tree.__BST_Node(value, knot.height + 1) 
                result = knot.lchild 
            else:
                result = self._insert(knot.lchild, value)
        else: # knot.value < value
            if knot.rchild is None:
                knot.rchild = Binary_Search_Tree.__BST_Node(value, knot.height + 1)
                result = knot.rchild
            else:
                result = self._insert(knot.rchild, value)
        self.__height = max(self.__height, result.height)
        return result

    def _remove(self, knot, value):
        """delete the knot with the given value
        based on: https://stackoverflow.com/a/33449471/8200213
        """
        if knot.value == value: # found the node we need to delete
            successor = None
            if knot.rchild and knot.lchild: 
                # get the successor node and its parent 
                successor, parent = knot.rchild, knot
                while successor.lchild:
                    successor, parent = successor.lchild, successor
                # splice out successor (the parent must do this) 
                if parent.lchild == successor:
                    parent.lchild = successor.rchild
                else:
                    parent.rchild = successor.rchild
                # reset the left and right children of the successor
                successor.lchild = knot.lchild
                successor.rchild = knot.rchild
                self._update_heights(successor, knot.height)
                return successor
            # else (not knot.rchild or/and not knot.lchild)
            if knot.lchild:     # promote the left subtree
                self._update_heights(knot.lchild, knot.height)
                return knot.lchild
            elif knot.rchild:   # promote the right subtree 
                self._update_heights(knot.rchild, knot.height)
                return knot.rchild
            # else: no children
            self._update_heights(knot, knot.height)
            return
        # else: keep traversing
        if knot.value > value and knot.lchild is not None:
            # value should be in the left subtree
            knot.lchild = self._remove(knot.lchild, value)
        elif knot.value < value and knot.rchild is not None:
            # value should be in the right subtree
            knot.rchild = self._remove(knot.rchild, value)
        # else: the value is not in the tree
        return knot

    def _update_heights(self, knot, height, maxheight=0):
        # print 'update from knot value', knot.value, 'with height', knot.height, 'to height', height
        maxheight = max(maxheight, knot.height)
        knot.height = height
        if knot.lchild is not None:
            self._update_heights(knot.lchild, knot.height + 1, maxheight)
        if knot.rchild is not None:
            self._update_heights(knot.rchild, knot.height + 1, maxheight)
        if maxheight == self.__height:
            # the max height of the whole tree might have changed; re-compute
            self.__height = -1

    def _recompute_height(self, knot):
        if not knot:
            return
        self.__height = max(self.__height, knot.height)
        if knot.lchild is not None:
            self._recompute_height(knot.lchild)
        if knot.rchild is not None:
            self._recompute_height(knot.rchild)

    def _get_ordered(self, knot, pre=False, nodelist=None):
        nodelist = nodelist or []
        if knot is None:
            return nodelist
        if not pre:
            nodelist = self._get_ordered(knot.lchild, pre, nodelist)
        nodelist.append(knot.value)
        if pre:
            nodelist = self._get_ordered(knot.lchild, pre, nodelist)
        nodelist = self._get_ordered(knot.rchild, pre, nodelist)
        return nodelist

    def insert_element(self, value):
        self._insert(self.__root, value)
        # print self.__height

    def remove_element(self, value):
        self.__root = self._remove(self.__root, value)
        # print self.get_height()

    def get_pre_order(self):
        return self._get_ordered(self.__root, True)

    def get_in_order(self):
        return self._get_ordered(self.__root)

    def get_height(self):
        if self.__height == -1:
            # self.__height was marked "dirty" 
            # re-computing tree height, as it might have changed
            self._recompute_height(self.__root)
        return self.__height

def __str__(self):
        return ', '.join(['%d' % val for val in self.get_in_order()])

正如link发布的PidgeyUsedGust中所建议的那样,您可能希望将高度存储在节点中,这样您就可以轻松计算任何新插入节点的高度和整个高度。树。

删除节点后保持高度最新并不是一件容易的事。我提出的是一个半天真的实现,旨在在代码简单性和性能之间进行适当的折衷(这意味着时间通常是可接受的但不是恒定的;如果有人知道更好的解决方案,我会非常乐意听听他们的意见。

以下是这个想法:

  • 每次删除节点时,都希望更新该节点及其所有后代的高度,而不更改树的其余部分。这就是_update_heights方法的作用
  • 这样做,每当我们点击一​​个节点时,其高度(更新前)等于整体高度,我们标记树属性&#34;脏&#34; (self.__height = -1
  • 下次我们在树上调用get_height,如果self.__height == -1我们通过调用{{1}知道它的高度可能已经改变并需要重新计算方法

_recompute_height仅由_recompute_height而非get_height触发的原因是为了避免每次删除元素时重新计算属性,如果这样做的话。不需要(也就是说,您可能需要删除1000个元素,然后只检查一次树的新高度)。

这里有几行来测试代码(使用了_update_heights模块:如果你还没有安装它,你需要安装它:

drawtree

以下是你应该得到的结果:

if __name__ == '__main__':

    bst = Binary_Search_Tree()
    values = [7, 2, 2, 5, 1, 8, 3, 6, 9, 8, 4, 11, 10, 12]
    print 'insert values', values
    for val in values:
        bst.insert_element(val)
    print 'tree successfully built\nheight: %d' % bst.get_height()
    print 'ordered: %s' % (bst, )
    print 'pre-ordered:', bst.get_pre_order(), '\n'

    import drawtree
    drawtree.draw_bst(bst.get_pre_order())

    values = [4, 2, 7, 4]
    for val in values:
        print '\n\nremove value %d\n' % val
        bst.remove_element(val)
        drawtree.draw_bst(bst.get_pre_order())

    print '\n\nnew height: %d' % bst.get_height()