数据结构(Python实现)------ 二叉搜索树
数据结构(Python实现)------二叉搜索树
二叉搜索树简介
基本概念
这篇文章之后,我们提供了一个习题来让你验证一个树是否是二叉搜索树。 你可以运用我们上述提到的性质来判断。 前一章介绍的递归思想也可能会对你解决这个问题有所帮助。
像普通的二叉树一样,我们可以按照前序、中序和后序来遍历一个二叉搜索树。 但是值得注意的是,对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。因此,中序遍历是二叉搜索树中最常用的遍历方法。
在文章习题中,我们也添加了让你求解二叉搜索树的中序后继节点(in-order successor)的题目。显然,你可以通过中序遍历来找到二叉搜索树的中序后继节点。 你也可以尝试运用二叉搜索树的特性,去寻求更好的解决方案。
Python实现
验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
方法一:
对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def isValidBST(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
def inorderTraversal(root):
if not root:
return []
res = []
res += inorderTraversal(root.left)
res.append(root.val)
res += inorderTraversal(root.right)
return res
res = inorderTraversal(root)
if res != sorted(list(set(res))) :
return False
return True
方法二:
如果左子树的值小于根的值并且右子树的值大于根的值,并进行递归,成立则为二叉搜索树,否则则不是。
class Solution2(object):
def isValidBST(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
min = max = None
return self.helper(root,min,max)
def helper(self,root,min,max):
if root is None:
return True
if min is not None and root.val <= min:
return False
if max is not None and root.val >= max:
return False
if self.helper(root.left,min,root.val) and self.helper(root.right,root.val,max):
return True
else:
return False
二叉搜索树迭代器
实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。
调用 next() 将返回二叉搜索树中的下一个最小的数。
第一种思路:
利用BST的性质:BST的中序遍历为升序数组。
所以直接在init里用中序遍历然后存进数组里就好了。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class BSTIterator(object):
def __init__(self, root):
"""
:type root: TreeNode
"""
self.res = []
def inorder(node):
if not node:
return
inorder(node.left)
self.res.append(node.val)
inorder(node.right)
inorder(root)
self.index = 0
def next(self):
"""
@return the next smallest number
:rtype: int
"""
self.index += 1
return self.res[self.index - 1]
def hasNext(self):
"""
@return whether we have a next smallest number
:rtype: bool
"""
return self.index < len(self.res)
# Your BSTIterator object will be instantiated and called as such:
# obj = BSTIterator(root)
# param_1 = obj.next()
# param_2 = obj.hasNext()
第二种思路:
利用一个stack来存。
pushLeft这个函数的功能是把 一个节点和它的左孩子以及左孩子的左孩子… 压入栈。
class BSTIterator2(object):
def __init__(self, root):
"""
:type root: TreeNode
"""
self.stack = []
self.pushLeft(root)
def next(self):
"""
@return the next smallest number
:rtype: int
"""
popedNode = self.stack.pop()
self.pushLeft(popedNode.right)
return popedNode.val
def hasNext(self):
"""
@return whether we have a next smallest number
:rtype: bool
"""
return len(self.stack) != 0
def pushLeft(self, node):
while(node):
self.stack.append(node)
node = node.left
在二叉搜索树中实现搜索操作
基本概念
在二叉搜索树中实现搜索操作
二叉搜索树主要支持三个操作:搜索、插入和删除。 在本章中,我们将讨论如何在二叉搜索树中搜索特定的值。
根据BST的特性,对于每个节点:
如果目标值等于节点的值,则返回节点;
如果目标值小于节点的值,则继续在左子树中搜索;
如果目标值大于节点的值,则继续在右子树中搜索。
我们一起来看一个例子:我们在上面的二叉搜索树中搜索目标值为 4 的节点。
在二叉搜索树中实现插入操作 - 介绍
二叉搜索树中的另一个常见操作是插入一个新节点。有许多不同的方法去插入新节点,这篇文章中,我们只讨论一种使整体操作变化最小的经典方法。 它的主要思想是为目标节点找出合适的叶节点位置,然后将该节点作为叶节点插入。 因此,搜索将成为插入的起始。
与搜索操作类似,对于每个节点,我们将:
根据节点值与目标节点值的关系,搜索左子树或右子树;
重复步骤 1 直到到达外部节点;
根据节点的值与目标节点的值的关系,将新节点添加为其左侧或右侧的子节点。
这样,我们就可以添加一个新的节点并依旧维持二叉搜索树的性质。
在二叉搜索树中实现删除操作
删除要比我们前面提到过的两种操作复杂许多。有许多不同的删除节点的方法,这篇文章中,我们只讨论一种使整体操作变化最小的方法。我们的方案是用一个合适的子节点来替换要删除的目标节点。根据其子节点的个数,我们需考虑以下三种情况:
- 如果目标节点没有子节点,我们可以直接移除该目标节点。
- 如果目标节只有一个子节点,我们可以用其子节点作为替换。
- 如果目标节点有两个子节点,我们需要用其中序后继节点或者前驱节点来替换,再删除该目标节点。
我们来看下面这几个例子,以帮助你理解删除操作的中心思想:
Python实现
Search in a Binary Search Tree
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def searchBST(self, root, val):
"""
:type root: TreeNode
:type val: int
:rtype: TreeNode
"""
if root:
def findsubtree(r,value):
if not r:
return None
elif r.val == value:
return r
else:
a = findsubtree(r.left,val)
if not a:
b=findsubtree(r.right,value)
return b
return a
return findsubtree(root,val)
return []
精致一点的递归:
class Solution(object):
def searchBST(self,root,val):
if not root:
return None
if root.val == val:
return root
elif root.val < val:
return self.searchBST(root.right,val)
else:
return self.searchBST(root.left,val)
Insert into a Binary Search Tree
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。
例如,
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution(object):
def insertIntoBST(self, root, val):
"""
:type root: TreeNode
:type val: int
:rtype: TreeNode
"""
if not root:
node = TreeNode(val)
return node
if val < root.val:
if not root.left:
node = TreeNode(val)
root.left = node
else:
self.insertIntoBST(root.left,val)
else:
if not root.right:
node = TreeNode(val)
root.right = node
else:
self.insertIntoBST(root.right,val)
return root
精简一些的递归
class Solution2(object):
def insertIntoBST(self, root, val):
if not root:
return TreeNode(val)
if val > root.val:
root.right = self.insertIntoBST(root.right,val)
if val < root.val:
root.left = self.insertIntoBST(root.left,val)
return root
Delete Node in a BST
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。
示例:
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def deleteNode(self, root, key):
"""
:type root: TreeNode
:type key: int
:rtype: TreeNode
"""
if not root:
return None
if root.val==key:
if not root.right:
left=root.left
return left
else:
right=root.right
while right.left:
right = right.left
root.val,right.val = right.val,root.val
root.left = self.deleteNode(root.left,key)
root.right = self.deleteNode(root.right,key)
return root
小结
基本概念
Python实现
Kth Largest Element in a Stream
方法一:直接降序排序,然后取第k个元素返回,add时每次都再排序一次,这样时间复杂度为O(k*logk)
class KthLargest(object):
def __init__(self, k, nums):
"""
:type k: int
:type nums: List[int]
"""
self.nums = nums
self.k = k
self.nums.sort(reverse = True)
while len(self.nums) >k:
self.nums.pop()
def add(self, val):
"""
:type val: int
:rtype: int
"""
self.nums.append(val)
self.nums.sort(reverse = True)
if len(self.nums) > self.k:
self.nums.pop()
return self.nums[-1]
# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)
方法二:使用小顶堆实现的优先队列,Python 中标准库 heapq 就是小顶堆,时间复杂度降低为O(k)
import heapq
class KthLargest2(object):
def __init__(self,k,nums):
self.pool = nums
heapq.heapify(self.pool)
self.k = k
while len(self.pool) > k:
heapq.heappop(self.pool)
def add(self,val):
if len(self.pool) < self.k:
heapq.heappush(self.pool,val)
elif val > self.pool[0]:
heapq.heapreplace(self.pool,val)
return self.pool[0]
二叉搜索树的最近公共祖先
非递归方法:
class Solution(object):
def lowestCommonAncestor(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
while root:
if root.val > max(p.val,q.val):
root = root.left
elif root.val < min(p.val,q.val):
root = root.right
else:
return root
return root
递归方法
class Solution2(object):
def lowestCommonAncestor(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
if root.val < p.val and root.val <q.val:
return self.lowestCommonAncestor(root.right,p,q)
elif root.val > p.val and root.val > q.val:
return self.lowestCommonAncestor(root.left,p,q)
else:
return root
存在重复元素 III
给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。
示例 1:
输入: nums = [1,2,3,1], k = 3, t = 0
输出: true
示例 2:
输入: nums = [1,0,1,1], k = 1, t = 2
输出: true
示例 3:
输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false
基本概念
什么是一个高度平衡的二叉搜索树?
树结构中的常见用语:
节点的深度 - 从树的根节点到该节点的边数
节点的高度 - 该节点和叶子之间最长路径上的边数
树的高度 - 其根节点的高度
一个高度平衡的二叉搜索树(平衡二叉搜索树)是在插入和删除任何节点之后,可以自动保持其高度最小。也就是说,有N个节点的平衡二叉搜索树,它的高度是logN。并且,每个节点的两个子树的高度不会相差超过1
根据定义, 我们可以判断出一个二叉搜索树是否是高度平衡的 (平衡二叉树)。
正如我们之前提到的, 一个有N个节点的平衡二搜索叉树的高度总是logN。因此,我们可以计算节点总数和树的高度,以确定这个二叉搜索树是否为高度平衡的。
同样,在定义中, 我们提到了高度平衡的二叉树一个特性: 每个节点的两个子树的深度不会相差超过1。我们也可以根据这个性质,递归地验证树。
为什么需要用到高度平衡的二叉搜索树?
我们已经介绍过了二叉树及其相关操作, 包括搜索、插入、删除。 当分析这些操作的时间复杂度时,我们需要注意的是树的高度是十分重要的考量因素。以搜索操作为例,如果二叉搜索树的高度为h,则时间复杂度为O(h)。二叉搜索树的高度的确很重要。
所以,我们来讨论一下树的节点总数N和高度h之间的关系。 对于一个平衡二叉搜索树, 我们已经在前文中提过, 。但对于一个普通的二叉搜索树, 在最坏的情况下, 它可以退化成一个链。
因此,具有N个节点的二叉搜索树的高度在logN到N区间变化。也就是说,搜索操作的时间复杂度可以从logN变化到N。这是一个巨大的性能差异。
所以说,高度平衡的二叉搜索树对提高性能起着重要作用。
如何实现一个高度平衡的二叉搜索树?
有许多不同的方法可以实现。尽管这些实现方法的细节有所不同,但他们有相同的目标:
采用的数据结构应该满足二分查找属性和高度平衡属性。
采用的数据结构应该支持二叉搜索树的基本操作,包括在O(logN)时间内的搜索、插入和删除,即使在最坏的情况下也是如此。
我们提供了一个常见的的高度平衡二叉树列表供您参考:
红黑树
AVL树
伸展树
树堆
我们不打算在本文中展开讨论这些数据结构实现的细节。
高度平衡的二叉搜索树的实际应用
高度平衡的二叉搜索树在实际中被广泛使用,因为它可以在O(logN)时间复杂度内执行所有搜索、插入和删除操作。
平衡二叉搜索树的概念经常运用在Set和Map中。 Set和Map的原理相似。 我们将在下文中重点讨论Set这个数据结构。
Set(集合)是另一种数据结构,它可以存储大量key(键)而不需要任何特定的顺序或任何重复的元素。 它应该支持的基本操作是将新元素插入到Set中,并检查元素是否存在于其中。
通常,有两种最广泛使用的集合:散列集合(Hash Set)和树集合(Tree Set)。
树集合, Java中的Treeset或者C++中的set,是由高度平衡的二叉搜索树实现的。因此,搜索、插入和删除的时间复杂度都是O(logN)。
散列集合, Java中的HashSet或者C++中的unordered_set,是由哈希实现的, 但是平衡二叉搜索树也起到了至关重要的作用。当存在具有相同哈希键的元素过多时,将花费O(N)时间复杂度来查找特定元素,其中N是具有相同哈希键的元素的数量。 通常情况下,使用高度平衡的二叉搜索树将把时间复杂度从 O(N) 改善到 O(logN)。
哈希集和树集之间的本质区别在于树集中的键是有序的。
Python实现
平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
第一种思路:
对于每个node,都判断一下其左右子树的相对高度,然后根据相对高度判断这个node满不满足条件,
再递归地判断其左右孩子满不满足条件。
这种思路会重复计算高度,所以会慢。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def isBalanced(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
if not root:
return True
def getHeight(node,h):
if not node:
return h
h+=1
return max(getHeight(node.left,h),getHeight(node.right,h))
l_height = getHeight(root.left,0)
r_height = getHeight(root.right,0)
return abs(l_height - r_height) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)
第二种思路:
如果每一步都计算树高,效率太低,故转变了思想。 递归判断每一个节点的均衡状态,并且只计算一次树高,直至根节点。
class Solution2(object):
def isBalanced(self,root):
if not root:
return True
def check(root,height):
if not root:
return True,height
tag1,height1 = check(root.left,height+1)
tag2,height2 = check(root.right,height+1)
if tag1 and tag2 and abs(height1-height2)<2:
return True,max(height1,height2)
else:
return False,height1
tag,height = check(root,0)
return tag
将有序数组转换为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
# Definition for a binary tree node.
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution(object):
def sortedArrayToBST(self, nums):
"""
:type nums: List[int]
:rtype: TreeNode
"""
if not nums:
return None
if len(nums) == 1:
return TreeNode(nums[0])
mid = len(nums) //2
root = TreeNode(nums[mid])
root.left = self.sortedArrayToBST(nums[:mid])
root.right = self.sortedArrayToBST(nums[mid+1:])
return root