二叉查找树BST

Posted ghostant

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉查找树BST相关的知识,希望对你有一定的参考价值。

1.什么是二叉查找树

在ADT MAP的实现方案中,可以采用不同的数据结构与搜索算法来保存和查找key。

  • 有序表数据结构+二分查找法
  • 散列表数据结构+散列及冲突解决算法

这里我们将尝试用二叉查找树保存key,实现key值的快速搜索。

2.二叉查找树的性质

比父节点小的key都出现在左子树中,比父节点大的key都出现在右子树中,这个性质可以看出二叉查找树时递归性质的。

              70
        31          93
    14     null   73   94
       23
            31 
        23      93
     14      73     94
           70

我们可以看到同一组数据(70,31,93,94,14,23,73)根据插入的顺序不同,生成的bst也是不一样的。

3.二叉树的实现及算法分析

class BinarySearchTree:
    def __init__(self):
        self.root = None
        self.size = 0
    
    def length(self):
        return self.size
    
    def __len__(self):
        return self.size
    
    def __iter__(self):
        return self.root.__iter__()
    
    def put(self,key,val):
        if self.root:
            self._put(key,val,self.root)
        else:
            self.root = TreeNode(key,val)
        self.size +=1
    
    # 思路:key小于当前节点的key,并且当前节点有左节点,则_put递归调用currentNode的节点为当前currentNode的左节点,如果当前节点没有左节点,则插入的key,val成为当前节点的左节点。同理。
    def _put(key,val,currentNode):
        if key < currentNode:
            if currentNode.hasLeftChild():
                self._put(key,val,currentNode.leftChild)
            else:
                currentNode.leftChild = TreeNode(key,val,parent=currentNode)
        else:
            if currentNode.hasRightChild:
                self._put(key,val,currentNode.rightChild)
            else:
                currentNode.rightChild = TreeNode(key,val,parent=currentNode)
    # tree_obj[k] = v时触发
    def __setitem__(self,k,v):
        self.put(key,val)
    
    def get(self,key):
        # 从根节点开始找
        if self.root:
            res = self._get(key,self.root)
            if res:
                return res.payload
            else:
                return None
        else:
            return None
    
    def _get(self,key,currentNode):
        if not currentNode:
            return None
        if currentNode.key == key:
            return currentNode
        elif currentNode.key > key:  
            return self._get(key,currentNode.leftChild)
        else:
            return self._get(key,currentNode.rightChild)
    
    def __getitem__(self,key):
        return self.get(key)
    
    def __contains__(self,key):
        if self._get(key,self.root):
            return True
        else:
            return False
    
    def delete(self,key):
        if self.size > 1:
            nodeToRemove = self._get(key,self.root)
            if nodeToRemove:
                self.remove(nodeToremove)
                self.size -=1
            else:
                raise KeyError('Error,key not in tree')
        elif self.size == 1 and self.root.key == key:
            self.root = None
            self.size = 0
        else:
            raise KeyError('Error,key not in tree')
    
    def __delitem__(self,key):
        self.delete(key)
    
'''
从BST中remove一个节点,还要求仍然保持BST的性质。
分情况讨论:
    1.该节点是叶子节点
    2.该节点只有左节点或右节点
    3.该节点同时有左节点和右节点
'''
    def remove(self,currentNode):
        if currentNode.isLeaf():
            if currentNode == currentNode.parent.leftChild:
                currentNode.parent.leftChild = None
            else:
                currentNode.parent.rightChild = None
        # 有两个节点
        '''
        此时无法就简单地进行替换被删节点,但可以找到另一个合适地节点来替换被删节点,整个合适节点就是被删节       点的下一个key值节点。即被删除节点右子树中做小的那个,称为‘后继’。 这个‘后继’ 只能是叶子节点或者是只有一个右节点。 
        '''
        elif currentNode.hasBothChild():
            succ = currentNode.findSuccessor()
            succ.spliceOut()
            currentNode.key = succ.key
            currentNode.payload = succ.payload
        
            
        # 只有一个节点
        else:
            if currentNode.hasLeftChild(): 
                if currentNode.isLeftChild(): # 当前节点有一个左节点并且它本身是左节点
                    currentNode.leftChild.parent = currentNode.parent
                    currentNode.parent.leftChild = currentNode.leftChild
                elif currentNode.isRightChild():# 当前节点有一个左节点并且它本身是右节点
                    currentNode.leftChild.parent = currentNode.parent
                    currentNOde.parent.rightChild = currentNode.rightChild
                else: # 当前节点有一个左节点,并且它本身既不是左节点也不是右节点(根节点)
                    # self.root = currentNode.leftChild
                    currentNode.replaceNodeData(
                        currentNode.leftChild.key,
                        currentNode.leftChild.payload,
                        currentNode.leftChild.leftChild,
                        currentNode.leftChild.rightChild)
            else:
                if currentNode.isLeftChild(): # 当前节点有一个右节点并且它本身是左节点
                    currentNode.rightChild.parent = currentNode.parent
                    currentNode.parent.leftChild = currentNode.rightChild
                elif currentNode.isRightChild(): # 当前节点有一个右节点并且它本身是右节点
                    currentNode.rightChild.parent = currentNode.parent
                    currentNode.parent.rightChild = currentNode.rightChild
                else:# 当前节点有一个右节点,并且它本身既不是左节点也不是右节点(根节点)
                    # self.root = currentNode.rightChild
                    currentNode.replaceNodeData(
                        currentNode.rightChild.key,
                        currentNode.rightChild.payload,
                        currentNode.rightChild.leftChild,
                        currentNode.rightChild.rightChild)
            
    
    
class TreeNode:
    def __init__(self,key,val,left=None,rigth=None,parent=None):
        self.key = key
        self.payload = val
        self.leftChild = left
        self.rightChild = right
        self.parent = parent
    
    def hasLeftChild(self):
        return self.leftChild
    
    def hasRightChild(self):
        return self.rightChild
    
    def isLeftChild(self):
        return self.parent and self.parent.leftChild == self
    
    def isRightChild(self):
        return self.parent and self.parent.rightChild == self
    
    def isRoot(self):
        return not self.parent
    
    def isLeaf(self):
        return not (self.rightChild or self.leftChild)
    
    def hasAnyChildren(self):
        return self.rightChild or self.leftChild
    
    def hasBothChild(self):
        return self.leftChild and self.rightChild
    
    def replaceNodeData(self,key,val,lc,rc):
        self.key = key
        self.payload = value
        self.leftChild = lc
        self.rightChild = rc
        if self.hasLeftChild():
            self.leftChild.parent = self
        if self.hasRightChild():
            self.rightChild.parent = self
            
    # 中序遍历     
    def __iter__(self):
        if self:
            if self.hasLeftChild():
                # 递归调用
                for elem in self.leftChild:
                    yield elem
            yield self.key
            if self.hasRihtChild():
                for elem in self.rightChild:
                    yield elem

    def findSuccessor(self):
        succ = None
        if self.hasRightChild():
            succ = self.rightChild.findMin()
        else: # 其实可以不考虑这个情况,findSuccessor被调用的前提时在当前节点同时有左右节点的情况下。
            if self.parent:
                if self.isLeftChild():
                    succ = self.parent
                else:
                    self.parent.rightChild = None
                    succ = self.parent.findSuccessor()
                    self.parent.rightChild = self
        return succ 
    
    def findMin(self):
        current = self
        # 迭代即可完成
        while current.hasLeftChild():
            current = current.leftChild
        return current
    
    
    # 通用节点摘除方法
    def spliceOut(self):
        if self.isLeaf():
            if self.isLeftChild():
                self.parent.leftChild = None
            else:
                self.parent.rightChild = None
        elif self.hasAnyChild():
            if self.hasLeftChild():
                if self.isLeftChild():
                    self.parent.leftChild = self.leftChild
                else:
                    self.parent.rightChild = self.leftChild
                self.leftChild.parent = self.parent
            else:
                if self.isLeftChild():
                    self.parent.leftChild = self.rightChild
                else:
                    self.parent.rightChild = self.rightChild
                self.rightChild.parent = self.parent
                

4.小结

# 算法分析 (以put为例)
# 其性能决定因素在于二叉搜索树的高度,而高度收到数据项key插入顺序的影响。
# 如果key的列表是随机分布的,那么大于和小于根节点key的键值大致相等,BST的高度就是logn(n是节点个数),而且这样的树就是平衡树,此时put方法最差性能为logn。

# 但是如果key列表分布的极端情况就完全不同。树向一边完全倾斜(类似于单列表的情况)有几个节点高度就为多少。

以上是关于二叉查找树BST的主要内容,如果未能解决你的问题,请参考以下文章

数据结构------------------二叉查找树(BST)的java实现

“中兴捧月”比赛之——二叉查找树(BST)树的最短路径Java求解

[二叉查找树] 1115. Counting Nodes in a BST (30)

数据结构什么是二叉查找树(BST)

二叉查找树BST

二叉排序树(BST)