算法(6):二叉查找树

  傻叉树算是先告一段落了,但是二叉树(Binary Tree)有很多种特殊情况,如平衡二叉树(Balanced Binary Tree)、二叉查找树(Binary Search Tree)、完全二叉树(Complete Binary Tree)、满二叉树(Full Binary Tree)等等,并且满二叉树的国内外定义还不同......所以又出现了个完美二叉树(Perfect Binary Tree,完美二叉树和我们国内的满二叉树定义相同),哎,任重而道远呐!



  老规矩,先撒泼打滚卖萌求关注,先介绍一下啥是二叉查找树。简单来说,对于查找树的每一个节点,该节点的值大于等于其左子树当中任何一个节点值,同样的,该节点的值也小于等于其右子树当中任何一个节点的值。
  如下图,便是一棵漂亮的BST:

1.png

  了解了概念,大家可以先试试我们上一堂算法课当中学到的前中后序以及层级遍历,对上面那棵BST遍历试试。你会惊讶的发现(额,也可能没那么惊讶),当你使用中序遍历时,结果刚好是升序排列。所以,中序遍历,应当是BST最常用的遍历方法了把~
  二叉查找树需要再提一提它的查找、增加、删除节点的操作。这些操作的时间复杂度都是 O(log(n)) (准确点说是 O(h),其中h代表树的深度),空间复杂度 O(1),强的要死!
  查找:从根节点走起,根节点比目标值大,那去左子树,小,则上右子树。等于的话,那就你了,跟我走~
  增加:很多策略,如果想要最小移动树节点的话,那就直接在叶子节点增加即可,跟查找同样步骤,一直到根节点,然后添加新节点即可。但是会导致这棵树长得较丑(不均衡)......
  删除:删除节点略显复杂,当该节点没有子节点时,直接删;如果有一个子节点,那么用那个子树代替该位置;如果有两个子节点,则用其有序后继节点(右子树当中最左边的值)或前任节点(左子树当中最右边的值)替换该节点并删除该节点。最后一种情况比较复杂,我给大家偷了个图供大家参考。我偷电动车养你啊......当然也有其他办法:如下图我还是找到了节点3,但是我将节点2的左子树贴在节点3的左子树上,然后将节点2的右子树4,放在原节点2的位置上(然后这棵树就变成了一个“人”字形......)。
删除有两个子树的节点

  基础概念就这么简单,知识的话,还是做题来的更夯实~


问题1:判断一棵树是否为BST。
  大家可能心想,我只需要中序遍历,然后看是否为升序排列即可~ 当然,是个好方法,但是下面代码是按照BST的定义来编写的,可以更方便大家认识BST的基本概念~
  该算法自顶向下,并记录了前面所有父节点属于的某个范围。如果下一个节点是左节点,则更新最大值(下一个节点应该小于该最大值和该节点的值);下一个节点是右节点,更新最小值(下一个节点应该大于该最小值和该节点的值)。有点绕,大家不要急,对,说你呢,别砸电脑了......慢慢理解。
输入:一棵可爱的二叉树,如下所示:

    3
   / \
  1   5
  \   /
  2   4

输出:True

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

def isValidBST(root, large=float('inf'), small=float('-inf')):
    if not root:
        return True
    if root.val <= small or root.val >= large:
        return False
    return isValidBST(root.left, min(large, root.val), small) and \
           isValidBST(root.right, large, max(root.val, small))

if __name__ == '__main__':
    node = head = TreeNode(3)
    node.left =  TreeNode(1)
    node.right = TreeNode(5)
    node.left.right = TreeNode(2)
    node.right.left = TreeNode(4)

    ans = isValidBST(head)
    print(ans)

问题2:写一个BST迭代器。实现两个功能,hasNext()判断是否已经迭代到尾部,next()返回接下来的一个值。返回顺序按照升序排列。
输入:同上题
输出:1 2 3 4 5

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class BSTIterator:
    def __init__(self, root: TreeNode):
        self.stack = []
        while root:
            self.stack.append(root)
            root = root.left

    # @return a boolean, whether we have a next smallest number
    def hasNext(self):
        return len(self.stack) > 0

    # @return an integer, the next smallest number
    def next(self):
        node = self.stack.pop()
        x = node.right
        while x:
            self.stack.append(x)
            x = x.left
        return node.val

if __name__ == '__main__':
    node = head = TreeNode(3)
    node.left =  TreeNode(1)
    node.right = TreeNode(5)
    node.left.right = TreeNode(2)
    node.right.left = TreeNode(4)

    obj = BSTIterator(head)
    while obj.hasNext():
        print(obj.next(),end=' ')

问题3:删除节点
输入:同问题1
输出:

    4
   / \
  1   5
  \   
  2   

本代码使用的方法就是上面所述的第一种删除方法~

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

def deleteNode(root: TreeNode, key: int) -> TreeNode:
    if not root:  # if root doesn't exist, just return it
        return root
    if root.val > key:  # if key value is less than root value, find the node in the left subtree
        root.left = deleteNode(root.left, key)
    elif root.val < key:  # if key value is greater than root value, find the node in right subtree
        root.right = deleteNode(root.right, key)
    else:  # if we found the node (root.value == key), start to delete it
        if not root.right:  # if it doesn't have right children, we delete the node then new root would be root.left
            return root.left
        if not root.left:  # if it has no left children, we delete the node then new root would be root.right
            return root.right
            # if the node have both left and right children,  we replace its value with the minmimum value in the right subtree and then delete that minimum node in the right subtree
        temp = root.right
        mini = temp.val
        while temp.left:
            temp = temp.left
            mini = temp.val
        root.val = mini  # replace value
        root.right = deleteNode(root.right, root.val)  # delete the minimum node in right subtree
    return root


if __name__ == '__main__':
    node = head = TreeNode(3)
    node.left =  TreeNode(1)
    node.right = TreeNode(5)
    node.left.right = TreeNode(2)
    node.right.left = TreeNode(4)

    ans = deleteNode(head,3)
    print(ans)

问题4:求最小公共祖先(Lowest Common Ancestor of a Binary Search Tree)
何为最小公共祖先?如下图,节点2和节点4的最小公共祖先为2(这里允许自己为自己的祖先,感觉这种逻辑,只有无性繁殖才能做到了把,,,),节点4和7的最小祖先为6,节点0和3的最小公共祖先为2......

1.png

二叉搜索树的找最小祖先比二叉树简单很多,我们可以充分利用其性质。除了最小公共祖先,其他的祖先有什么规律呢?我们发现,其他的祖先,会同时大于/(小于)这两个节点!而最小公共祖先,恰好是分岔口,要么大于其中一个,小于另一个,要么等于其中一个。知否?知否?应是绿肥红瘦...那就上代码~

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

def lowestCommonAncestor(root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
    while root:
        if root.val > p.val and root.val > q.val:
            root = root.left
        elif root.val < p.val and root.val < q.val:
            root = root.right
        else:
            return root

if __name__ == '__main__':
    node = head = TreeNode(3)
    node.left =  TreeNode(1)
    node.right = TreeNode(5)
    node.left.right = TreeNode(2)
    node.right.left = TreeNode(4)

    ans = lowestCommonAncestor(head,node.left,node.left.right)
    print(ans.val)

问题5

# 四个问题不吉利,小编拍胸脯保证,马上(百年之内)把第五个问题补起来!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342