(2022.05.17 Tues)
前面提到的二叉树的应用之一是搜索树。我们这里用树结构实现顺序映射/有序映射(sorted map),其中的三个基础方法是
-
M[k]
:返回映射中keyk
的关联值;用__getitem__
方法实现;如果没有则返回KeyError
错误 -
M[k] = p
:对映射中keyk
的关联值赋值,如果不存在则新建,如果存在则覆盖;用__setitem__
方法实现 -
del M[k]
:删除映射中的keyk
和关联值
假定存入树的的key按序排列,则二叉树是实现映射的良好数据结构。二叉搜索树满足下面条件,当位置p
保存了键值对(k, v)
-
p
左子树的key小于k
-
p
右子树的key大于k
对二叉搜索树做中序遍历,遍历结果按单调顺序排列。
(2022.05.18 Wed)
搜索
在二叉搜索树中执行搜索,对于给定的key k
,从根节点root开始,比较root点的key r
值
- 如果
k
>r
,则root的右子树与k
做比较 - 如果
k
< 'r',则root的左子树与k
作比较 - 如果
k
=r
,则root就是索要找的点,返回 - 如果左/右子树为空,则key
k
不在该二叉搜索树中,返回搜索k
得到的最后一个节点p
复杂度分析:搜索步数和搜索树的最大高度有关,复杂度是。最坏的情况,该二叉树只有一个叶节点,其余节点的度都为1,则这样的搜索将会遍历每一个节点,最坏情况复杂度变为。
插入和删除
插入相对删除比较简单。插入(k, v)
到一个搜索二叉树上,首先确定需要插入的值不在二叉树上。如果在该树上,则直接修改对应节点的值。如果不在该二叉树上,根据前面的搜索算法找到节点p,如果p点对应的key 有,则(k, v)
加在p的左子树上,否则加在右子树上。插入操作总是发生在路径底端。
插入的复杂度包括两部分,一部分是搜索(),另一部分是加子树(),总复杂度是。
删除操作略复杂。首先执行搜索操作,找到需要删除的节点p。该节点有一下两种情况
- 该节点p没有子树,则直接删除
- 该节点有一个子节点,删除之后除了子节点外其他关系不做变化,子节点挪到该节点所在位置代替
- 该节点p有两个子节点,删除p之后会空出一个位置。从p的左子树中找到key最大的点k,该点是所有小于p的节点里最大的点。将k点移动到p的点上。此时k点的位置控制。考虑到k点是小于p的节点里最大的,所以k点没有右节点,于是k点子树的处理可以按照上面的一个子节点的情况来处理。
平衡搜索树Balanced Search Tree
上面介绍的二叉搜索树最坏的情况是整棵树只有一个leaf,而每个中间节点只有一个子树。对这棵树做检索操作复杂度达到。需要对该树进行处理使其成为一个检索复杂度保持在。
使搜索树维持平衡的常用方法是旋转rotation,即子树通过旋转到其parent上成为parent,而原来的parent成为child。
搜索二叉树的实现
from collections import MutableMapping
class MapBase(MutableMapping):
class _Item:
__slots__ = '_key', '_value'
def __init__(self, k, v):
self._key = k
self._value = v
def __eq__(self, other):
return self._key == other._key
def __ne__(self, other):
return not (self == other)
def __lt__(self, other):
return self._key < other._key
def key(self):
return self._key
def value(self):
return self._value
def __len__(self, *args):
return super().__len__(*args)
class TreeMap(LinkedBinaryTree, MapBase):
def _subtree_search(self, p, k):
if k == p.key():
return p
elif k < p.key():
if self.left(p) is not None:
return self._subtree_search(self.left(p), k)
else:
if self.right(p) is not None:
return self._subtree_search(self.right(p), k)
return p
def _subtree_first_position(self, p):
'''Return Position of first item in subtree rooted at p.'''
walk = p
while self.left(walk) is not None: # keep walking left
walk = self.left(walk)
return walk
def _subtree_last_position(self, p):
'''Return Position of last item in subtree rooted at p.'''
walk = p
while self.right(walk) is not None: # keep walking right
walk = self.right(walk)
return walk
def first(self):
"""Return the first Position in the tree (or None if empty)."""
return self._subtree_first_position(self.root()) if len(self) > 0 else None
def last(self):
"""Return the last Position in the tree (or None if empty)."""
return self._subtree_last_position(self.root()) if len(self) > 0 else None
def before(self, p):
"""Return the Position just before p in the natural order.
Return None if p is the first position."""
if self.left(p):
return self._subtree_last_position(self.left(p))
else:
# walk upward
walk = p
above = self.parent(walk)
while above is not None and walk == self.left(above):
walk = above
above = self.parent(walk)
return above
def after(self, p):
"""Return the Position just after p in the natural order.
Return None if p is the last position."""
# symmetric to before(p)
def find_min(self):
"""Return (key,value) pair with minimum key (or None if empty)."""
if self.is_empty():
return None
else:
p = self.first()
return (p.key(), p.value())
def __getitem__(self, k):
"""Return value associated with key k (raise KeyError if not found)."""
if self.is empty( ):
raise KeyError("Key Error: "+ repr(k))
else:
p = self._subtree_search(self.root(), k)
# self._rebalance_access(p) # hook for balanced tree subclasses
if k != p.key():
raise KeyError("Key Error: "+ repr(k))
return p.value()
def __setitem__(self, k, v):
"""Assign value v to key k, overwriting existing value if present."""
if self.is empty():
leaf = self._add_root(self._Item(k,v)) # from LinkedBinaryTree
else:
p = self._subtree_search(self.root(), k)
if p.key() == k:
p.element()._value = v # replace existing item’s value
self._rebalance_access(p) # hook for balanced tree subclasses
return
else:
item = self._Item(k,v)
if p.key() < k:
leaf = self._add_right(p, item) # inherited from LinkedBinaryTree
else:
leaf = self._add_left(p, item) # inherited from LinkedBinaryTree
# self._rebalance_insert(leaf)
def __iter__(self):
"""Generate an iteration of all keys in the map in order."""
p = self.first()
while p is not None:
yield p.key( )
p = self.after(p)
def delete(self, p):
"""Remove the item at given Position."""
# self. validate(p) # inherited from LinkedBinaryTree
if self.left(p) and self.right(p): # p has two children
replacement = self._subtree_last_position(self.left(p))
self. replace(p, replacement.element( )) # from LinkedBinaryTree
p = replacement
# now p has at most one child
parent = self.parent(p)
self._delete(p) # inherited from LinkedBinaryTree
self. rebalance delete(parent) # if root deleted, parent is None
def __delitem__(self, k):
"""Remove item associated with key k (raise KeyError if not found)."""
if not self.is_empty():
p = self. subtree search(self.root(), k)
if k == p.key():
self.delete(p) # rely on positional version
return # successful deletion complete
# self._rebalance_access(p) # hook for balanced tree subclasses
raise KeyError("Key Error: "+ repr(k))
Reference
1 Goodrich and etc., Data Structures and Algorithms in Python
网友评论