【问题标题】:How to get height in constant time in a binary search tree? [duplicate]如何在二叉搜索树中以恒定时间获得高度? [复制]
【发布时间】:2017-06-28 08:29:13
【问题描述】:

所以我有一个二叉搜索树,我正在尝试获取所述树的高度。我有一个 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

【问题讨论】:

  • 有答案here
  • 一方面,你错误地处理了已经存在的值,插入了if root == value:。此外,在通过比较值评估它应该在哪里之前,您不应该真正创建节点元素。

标签: python recursion binary-search-tree


【解决方案1】:

这是你的树的一个版本,应该可以完成这项工作。 我试图使其尽可能与您的原始代码相似,但不得不对其进行一些更改,同时试图弄清楚如何使示例工作。

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

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()])

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

在移除节点后保持高度更新不是那么简单。我想出的是一个半天真的实现,旨在在代码简单性和性能之间做出适当的折衷(这意味着时间通常是可以接受的,但不是恒定的;如果有人知道更好的解决方案,我会很高兴听到他们的拿)。

这是一个想法:

  • 每次删除节点时,您都​​希望更新该节点及其所有子节点的高度,而树的其余部分保持不变。这就是_update_heights 方法的作用
  • 这样做时,每次我们点击一​​个节点,其高度(更新前)等于总高度,我们将树属性标记为“脏”(self.__height = -1
  • 下次我们将在树上调用get_height,如果self.__height == -1 我们会知道它的高度可能已经改变并且需要通过调用_recompute_height 方法重新计算

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

这里有几行代码来测试代码(使用了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()

这是你应该得到的结果:

insert values [7, 2, 2, 5, 1, 8, 3, 6, 9, 8, 4, 11, 10, 12]
WARNING: value 2 already exists; skipped
WARNING: value 8 already exists; skipped
tree successfully built
height: 5
ordered: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
pre-ordered: [7, 2, 1, 5, 3, 4, 6, 8, 9, 11, 10, 12] 

     7
    / \
   /   \
  2     8
 / \     \
1   5     9
   / \     \
  3   6    11
   \       / \
    4     /   \
         10   12


remove value 6

    7
   / \
  2   8
 / \   \
1   5   9
   /     \
  3      11
   \     / \
    4   /   \
       10   12


remove value 2

    7
   / \
  3   8
 / \   \
1   5   9
   /     \
  4      11
         / \
        /   \
       10   12


remove value 7

     8
    / \
   /   \
  3     9
 / \     \
1   5    11
   /     / \
  4     /   \
       10   12


new height: 4

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多