文章目录
- 一、二叉搜索树简介
- 二、二叉搜索树的节点定位
- 三、二叉搜索树的查询操作
- 四、二叉搜索树的插入操作
- 五、二叉搜索树的删除操作
- 六、二叉搜索树实现有序映射
- 1. 节点描述类`Position`
- 2. 实用查询方法`_subtree_search`
- 3. 查询“第一个”节点实用方法`_subtree_first_position`
- 4. 查询“最后一个”节点实用方法`_subtree_last_position`
- 5. 查询“第一个”节点方法`first`
- 6. 查询“最后一个”节点方法`last`
- 7. 中序遍历时定位前一个位置方法`before`
- 8. 中序遍历时定位后一个位置方法`after`
- 9. 查找指定键所在位置方法`find_position`
- 10. 查找并返回键最小的键值对方法`find_min`
- 11. 查找并返回不小于指定键的键值对方法`find_ge`
- 12. 根据区间查找键值对的方法`find_range`
- 13. 支持映射通过`M[k]`获取值的方法`__getitem__`
- 14. 支持映射通过`M[k] = v`设置键值对方法`__setitem__`
- 15. 生成映射所有键的迭代方法`__iter__`
- 16. 删除给定位置节点的方法`delete`
- 17. 支持映射通过`del M[k]`删除键值对方法`__delitem__`
- 七、有序映射操作时间复杂度
- 八、二叉搜索树实现有序映射完整测试代码
在文章【数据结构Python描述】使用列表手动实现一个有序映射中,我们实现了第一个有序映射,虽然当待操作键值对存在时,利用二分查找可以将查询、修改操作的最坏时间复杂度保证在 O ( l o g n ) O(logn) O(logn),但是当待操作键值对不存在时,由于增、删操作可能在列表头部进行,此时增、删操作引起的每个元素平移将使得最坏时间复杂度变为 O ( n ) O(n) O(n)。
针对上述问题,在【数据结构Python描述】跳跃表简介及使用跳跃表实现有序映射中,我们又使用跳跃表实现了一个有序映射,跳跃表由于本质是链表,因此避免了元素增、删后可能引起的其他元素位置变动,对于链表遍历效率低的问题,跳跃表通过增加节点的引用数量的方式来解决,因此通过跳跃表实现的有序映射可实现增、删、改、查操作在大概率情况下都是 O ( l o g n ) O(logn) O(logn)。
本文将使用一种特殊的二叉树——二叉搜索树来实现有序映射。
一、二叉搜索树简介
定义:二叉搜索树是一种特殊的二叉树,相较于普通二叉树,其特殊之处在于节点位置
p
处保存的元素(k, v)
和其左右子树满足下列关系:
- 位置
p
的左子树中所有节点的元素的键都小于k
;- 位置
p
的右子树中所有节点的元素的键都大于k
;
下面是一个典型的二叉搜索树,为简便起见,这里仅显示了每个节点处的键,而省略了每个节点处键对应的值,因为值不影响节点间的相对关系:
二、二叉搜索树的节点定位
1. 二叉搜索树与有序映射
二叉搜索树之所以可以用来表示一个有序映射,原因在于对一个二叉搜索树使用二叉树的中序遍历算法将按照节点的键升序依次遍历树中的每一个节点。
2. 二叉搜索树的节点定位
尽管二叉树的中序遍历一般通过自上而下的递归来实现,而对于二叉搜索树,按照节点间键的相对大小关系,我们还可以提供粒度更细的节点定位方法。
因此,这里除了提供一般二叉树的节点定位方法parent(p)
、left(p)
以及right(p)
(其中p
代表抽象节点位置的实例对象)之外,还提供一下几个方法:
first()
:返回包含最小键的节点的位置,当二叉搜索树树为空时返回None
;last()
:返回包含最大键的节点的位置,当二叉搜索树树为空时返回None
;after(p)
:对于键大于位置p
处节点的键的所有节点,返回其中键最小的节点的位置(即中序遍历中,位置p
的下一个位置),当p
代表最后一个节点的位置,则返回None
;before(p)
:对于键小于位置p
处节点的键的所有节点,返回其中键最大的节点的位置(即中序遍历中,位置p
的上一个位置),当p
代表第一个节点的位置,则返回None
。
first()
根据二叉搜索树的性质,为找到其键最小的节点,只要从根节点开始重复调用left(p)
方法直到某节点不存在左子节点,则该节点位置即为所需的节点位置。
last()
类似上述first()
方法,根据二叉搜索树的性质,为找到其键最大的节点,只要从根节点开始重复调用right(p)
方法直到某节点不存在右子节点,则该节点位置即为所需的节点位置。
after(p)
对于二叉搜索树,为找到中序遍历时位置p
处节点的后继节点位置,可通过下列算法实现:
Algorithm after(p):
// successor is leftmost position in p’s right subtree
if right(p) is not None then
walk = right(p)
while left(walk) is not None do
walk = left(walk)
return walk
// successor is nearest ancestor having p in its left subtree
else
walk = p
ancestor = parent(walk)
while ancestor is not None and walk == right(ancestor) do
walk = ancestor
ancestor = parent(walk)
return ancestor
上述算法完全是基于中序遍历的原理以及二叉搜索树的性质,即:
- 如果
p
处节点有右子树,则对p
处节点进行访问后,紧随其后被访问的是该右子树中最左侧位置的节点; - 如果
p
处节点无右子树,则在该节点处的中序遍历的递归调用将先返回到p
的父节点,而:- 如果
p
处节点在其父节点的左子树中,则该父节点就是后继节点; - 如果
p
处节点在其父节点的右子树中,则说明其父节点处左右子树的中序遍历均已完成,此时。
- 如果
before(p)
对于二叉搜索树,为找到中序遍历时位置p
处节点的前序节点位置,可通过下列算法实现:
Algorithm before(p):
// predecessor is rightmost position in p’s left subtree
if left(p) is not None then
walk = left(p)
while right(walk) is not None do
walk = right(walk)
return walk
// predecessor is nearest ancestor having p in its right subtree
else
walk = p
ancestor = parent(walk)
while ancestor is not None and walk == left(ancestor) do
walk = ancestor
ancestor = parent(walk)
return ancestor
实际上,上述实现before(p)
的算法和after(p)
的算法具有完全的对称性。
三、二叉搜索树的查询操作
当需要根据键查找二叉搜索树的某节点时,根据二叉搜索树的性质,只需要从根节点处开始递归地做如下判断:
- 当待查找的键小于位置
p
处节点的键,则查询在以p
的左子节点为根节点的左子树继续递归; - 当待查找的键等于位置
p
处节点的键,则返回p
; - 当待查找的键大于位置
p
处节点的键,则查询在以p
的右子节点为根节点的右子树继续递归。
递归过程中,除了上述第二种情况递归返回,另一种递归返回的情况发生在遇到空子树时。
下图分别表示了成功搜索到键为65的节点及未能成功搜索到键为68的节点两种情况:
下面给出二叉搜索树的查询操作的算法伪代码:
Algorithm TreeSearch(T, p, k):
if k == p.key() then
// successful search
return p
else if k < p.key() and T.left(p) is not None then
// recur on left subtree
return TreeSearch(T, T.left(p), k)
else if k > p.key() and T.right(p) is not None then
// recur on right subtree
return TreeSearch(T, T.right(p), k)
// unsuccessful search
return p
需要注意的是,在查询操作失败时,上述算法会返回最后访问的节点所在位置,这对于后续实现节点插入操作很有帮助。
四、二叉搜索树的插入操作
基于__setitem__
方法实现的映射操作M[k] = v
依赖于先根据键k
进行查找节点(假定映射非空),如果找到键同样为k
的节点,则节点处的值被修改为v
;否则键值对将被插入二叉搜索树中,且位置为查询操作最后所到达节点的空子树处,下面是二叉搜索树插入操作的伪代码:
Algorithm TreeInsert(T, k, v):
p = TreeSearch(T, T.root(),k)
if k == p.key() then
Set p’s value to v
else if k < p.key() then
add node with item (k, v) as left child of p
else
add node with item (k, v) as right child of p
下图表示了向二叉搜索树中插入键为68的节点所需的步骤,即首先如图(a)所示将先搜索到键为76的节点处并确定应该将新节点插在该节点的左子节点处,然后如图(b)所示将该节点插在键为76的节点的左子节点位置。
五、二叉搜索树的删除操作
相较于向二叉搜索树T
中插入一个节点,从T
中删除一个节点要复杂不少,因为节点插入操作永远发生在树的一条路径的终点,相反待删除的节点可能出现在T
的任意位置处。
因此,为了删除某节点,需要先调用TreeSearch(T, T.root(), k)
找到键为k
的节点所在位置p
,如果成功找到了待删除节点,我们还需按照下列两种情况进行区分:
1. 至多一个子节点情况下的节点删除
如位置 p
处的节点至多只有一个子节点,可以使用之前一般二叉树 LinkedBinaryTree
实现类中的_delete(p)
方法,该方法会删除位置 p
处的节点,并且在该位置有子节点时(至多一个)使用其子节点填充该位置。下图即描述了当删除键为
32
32
32 的节点,且该节点仅有一个键为
28
28
28 的子节点时的操作流程。
2. 恰好两个子节点情况下的节点删除
如位置 p
处的节点有两个子节点,此时以下图为例,当待删除键为
88
88
88 的节点,且该节点有键分别为
65
65
65 和
97
97
97 的两个子节点,此时节点删除操作遵循下列步骤:
- 首先,通过
r = before(p)
获得p
处节点的前序节点所在位置r
,由于位置p
处节点有两个子节点,故r
为p
的左子树中“最右侧”节点的位置,如下图所示即键为 82 82 82 的节点所在位置; - 然后,使用位置
r
处的节点替代被删除的位置p
处节点,这么做可以确保二叉树不违反二叉搜索树的性质。因为在中序遍历中,程序访问r
处节点后将立即访问p
处节点,且位置p
处的右子树中所有节点的键都大于r
处节点的键,而在位置p
处的左子树中,r
处节点的键最大; - 最后,将原本位置
r
处的节点删除,奇妙的是由于位置r
是某子树“最右侧”位置,因此该位置至多有一个子节点,故可以使用第一种简单情形删除该节点。
六、二叉搜索树实现有序映射
下面我们将使用二叉搜索树来实现一个有序映射类TreeMap
,这里为提高代码的复用程度并再次体现模板设计模式的优势,此处利用多继承,即TreeMap
类同时继承一般二叉树实现类LinkedBinaryTree
和映射基类MapBase
,其中:
- 通过继承映射基类
MapBase
,我们可以得到用于表示键值对的_Item
类以及collections.MutableMapping
抽象基类中所有的映射ADT方法; - 通过继承一般二叉树实现类
LinkedBinaryTree
,我们一方面可以复用一般二叉树的所有ADT方法,从而只需实现二叉搜索树的特有方法,另外还可以通过重写其中的Position
类来提供更简单的键、值访问方法p.key()
和p.value()
。
1. 节点描述类Position
该类嵌套定义在TreeMap
类中且继承LinkedBinaryTree
中的Position
类。
class Position(LinkedBinaryTree.Position):
"""用于表示二叉搜索树中节点位置的类"""
def key(self):
"""返回映射中某键值对的键"""
return self.element().key
def value(self):
"""返回映射中某键值对的值"""
return self.element().value
2. 实用查询方法_subtree_search
该方法即为TreeSearch(T, p, k)
算法的具体实现。
def _subtree_search(self, p, k):
"""针对根节点在位置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 # 查找失败
3. 查询“第一个”节点实用方法_subtree_first_position
def _subtree_first_position(self, p):
"""返回以位置p处节点为根节点的二叉搜索(子)树中“第一个”节点的位置"""
walk = p
while self.left(walk) is not None:
walk = self.left(walk)
return walk
4. 查询“最后一个”节点实用方法_subtree_last_position
def _subtree_last_position(self, p):
"""返回以位置p处节点为根节点的二叉搜索(子)树中“最后一个”节点的位置"""
walk = p
while self.right(walk) is not None:
walk = self.right(walk)
return walk
5. 查询“第一个”节点方法first
def first(self):
"""返回该二叉搜索树第一个节点的位置"""
return self._subtree_first_position(self.root()) if len(self) > 0 else None
6. 查询“最后一个”节点方法last
def last(self):
"""返回该二叉搜索树最后一个节点的位置"""
return self._subtree_last_position(self.root()) if len(self) > 0 else None
7. 中序遍历时定位前一个位置方法before
def before(self, p):
"""返回中序遍历时,位置p的前一个位置,当位置p为第一个位置时返回None"""
self._pos2node(p)
if self.left(p):
return self._subtree_last_position(self.left(p))
else:
walk = p
ancestor = self.parent(walk)
while ancestor is not None and walk == self.left(ancestor):
walk = ancestor
ancestor = self.parent(walk)
return ancestor
8. 中序遍历时定位后一个位置方法after
def after(self, p):
"""返回中序遍历时,位置p的后一个位置,当位置p为最后一个位置时返回None"""
self._pos2node(p)
if self.right(p):
return self._subtree_first_position(self.right(p))
else:
walk = p
ancestor = self.parent(walk)
while ancestor is not None and walk == self.right(ancestor):
walk = ancestor
ancestor = self.parent(walk)
return ancestor
9. 查找指定键所在位置方法find_position
def find_position(self, k):
"""
如果有序映射非空,则当有序映射中存在键为k的键值对,
则返回该键值对所在节点在二叉搜索树中的位置,
不存在则返回最后到达的位置,否则返回None
"""
if self.is_empty():
return None
else:
return self._subtree_search(self.root(), k)
10. 查找并返回键最小的键值对方法find_min
def find_min(self):
"""
如有序映射非空,则返回有序映射中键最小的键值对所在节点在二叉搜索树中的位置,
否则返回None
"""
if self.is_empty():
return None
else:
p = self.first()
return p.key(), p.value()
11. 查找并返回不小于指定键的键值对方法find_ge
def find_ge(self, k):
"""查找并返回键不小于k的键值对,如不存在这样的键值对则返回None"""
if self.is_empty():
return None
else:
p = self.find_position(k)
if p.key() < k:
p = self.after(p)
return p.key(), p.value() if p is not None else None
12. 根据区间查找键值对的方法find_range
def find_range(self, start, stop):
"""
迭代所有满足start <= key < stop的键值对(key,value),
且如果start为None,则迭代从最小的键开始,
如果stop为None,则迭代到最大的键结束。
"""
if not self.is_empty():
if start is None:
p = self.first()
else:
p = self.find_position(start)
if p.key() < start:
p = self.after(p)
while p is not None and (stop is None or p.key() < stop):
yield p.key(), p.value()
p = self.after(p)
13. 支持映射通过M[k]
获取值的方法__getitem__
def __getitem__(self, k):
"""返回有序映射中键k所对应的值,如键k不存在则抛出KeyError异常"""
if self.is_empty():
raise KeyError('Key Error: ' + repr(k))
else:
p = self._subtree_search(self.root(), k)
if k != p.key():
raise KeyError('Key Error: ' + repr(k))
return p.value()
14. 支持映射通过M[k] = v
设置键值对方法__setitem__
def __setitem__(self, k, v):
"""向有序映射中插入键值对(k, v),当k存在时替换原有的值"""
if self.is_empty():
self._add_root(self._Item(k, v))
else:
p = self._subtree_search(self.root(), k)
if p.key() == k:
p.element().value = v
return
else:
item = self._Item(k, v)
if p.key() < k:
self._add_right(p, item)
else:
self._add_left(p, item)
15. 生成映射所有键的迭代方法__iter__
def __iter__(self):
"""生成映射所有键的一个迭代"""
p = self.first()
while p is not None:
yield p.key()
p = self.after(p)
16. 删除给定位置节点的方法delete
def delete(self, p):
"""删除给定位置处的节点"""
self._pos2node(p) # 确保待操作位置的节点依然有效
if self.left(p) and self.right(p): # 位置p处有两个子节点
replacement = self._subtree_last_position(self.left(p))
self._replace(p, replacement.element()) # 将位置p处节点的键值对进行替换
p = replacement
# 这时位置p处至多有一个子节点
self._delete(p)
17. 支持映射通过del M[k]
删除键值对方法__delitem__
def __delitem__(self, k):
"""删除键为k的节点,当键k不存在时抛出KeyError异常"""
if not self.is_empty():
p = self._subtree_search(self.root(), k)
if k == p.key():
self.delete(p)
return
raise KeyError('Key Error: ' + repr(k))
七、有序映射操作时间复杂度
下表给出了上述实现的TreeMap
中所有操作的最坏时间复杂度,基本所有操作的最坏时间复杂度都取决于
h
h
h,其中
h
h
h为当前二叉搜索树的高度,这是因为绝大多数的操作都依赖于沿着某条路径对其上每个节点进行操作,一方面对每个节点的操作都是定长时间,另一方面最大的路径长度和二叉搜索树的高度呈正比。
映射操作 | 最坏时间复杂度 |
---|---|
M[k] | O ( h ) O(h) O(h) |
M[k] = v | O ( h ) O(h) O(h) |
del M[k] | O ( h ) O(h) O(h) |
M.delete(p) | O ( h ) O(h) O(h) |
M.find_position(k) | O ( h ) O(h) O(h) |
M.first() ,M.last() ,M.find_min() ,M.find_max() | O ( h ) O(h) O(h) |
M.before(p) ,M.after(p) | O ( h ) O(h) O(h) |
M.find_lt(k) ,M.find_le(k) ,M.find_gt(k) ,M.find_ge(k) | O ( h ) O(h) O(h) |
M.find_range(start,stop) | O ( s + h ) O(s+h) O(s+h) |
__iter__ , __reversed__ | O ( n ) O(n) O(n) |
由以上分析可知,只有在二叉搜索树T
的高度很小时,T
才是实现有序映射的高效方式,而在最好的情况下T
的高度为
h
=
⌈
l
o
g
(
n
+
1
)
⌉
−
1
h=\lceil{log(n+1)}\rceil-1
h=⌈log(n+1)⌉−1,即此有序映射的所有操作的最坏时间复杂度为
l
o
g
(
n
)
log(n)
log(n)。
然而,在最坏的情况下,T
的高度为
n
n
n,此时二叉搜索树退化为一条单链表(如下图所示),当按照键的大小升序或降序插入节点时会出现这种最坏情况。
幸运的是,只要我们在向搜索二叉树中插入或删除节点时是随机的,则可以保证对二叉搜索树的操作最坏时间复杂度为 O ( l o g ( n ) ) O(log(n)) O(log(n))。关于这一结论的详细证明不是本文所要继续讨论的话题。
八、二叉搜索树实现有序映射完整测试代码
from abc import ABCMeta, abstractmethod
from collections.abc import MutableMapping
class MapBase(MutableMapping):
"""提供用于保存键值对记录类的自定义映射基类"""
class _Item:
__slots__ = 'key', 'value'
def __init__(self, key, value):
self.key = key
self.value = value
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 __str__(self):
"""返回映射对象的字符串表示形式"""
return str(dict(self.items()))
class Tree(metaclass=ABCMeta):
"""表示一般树的抽象基类"""
class Position(metaclass=ABCMeta):
"""嵌套定义的位置类,其实例对象用于描述结点在树中的位置"""
@abstractmethod
def element(self):
"""
用于返回某位置处的对象元素
:return: 结点处的对象元素
"""
@abstractmethod
def __eq__(self, other):
"""
使用'=='判断两个Position实例是否代表同一个位置
:param other: 另一个Position的实例
:return: Boolean
"""
@abstractmethod
def __ne__(self, other):
"""
使用'!='判断两个Position实例是否不代表同一个位置
:param other: 另一个Position的实例
:return: Boolean
"""
@abstractmethod
def __len__(self):
"""
返回树中所有结点数量
:return: 结点数量
"""
@abstractmethod
def root(self):
"""返回代表数根结点的位置对象,如树为空则返回None"""
@abstractmethod
def parent(self, p):
"""
返回位置p处结点的父结点所在位置,如p处为根结点则返回None
:param p: 某结点所在位置
:return: 某结点的父结点所在位置
"""
@abstractmethod
def num_children(self, p):
"""
返回位置p处结点的子结点数目
:param p: 结点位置
:return: 结点的子结点数目
"""
@abstractmethod
def children(self, p):
"""
生成位置p处结点的所有子结点的一个迭代
:param p: 结点位置
:return: 子结点位置
"""
def preorder(self):
"""
产生一个树的所有结点位置的前序迭代
:return:
"""
if not self.is_empty():
for p in self._subtree_preorder(self.root()):
yield p
def _subtree_preorder(self, p):
"""
针对根结点在位置p处的子树,产生关于子树所有结点位置的一个前序迭代
:param p: 结点位置
:return:
"""
yield p # 后续先访问位置p处结点,再访问以其为根结点的子树中所有结点
for child in self.children(p): #
for each in self._subtree_preorder(child): # 对child的子树做前序遍历
yield each # 懒惰式生成结点位置给生成器调用者
def postorder(self):
"""
产生一个树的所有结点位置的后序迭代
:return:
"""
if not self.is_empty():
for p in self._subtree_postorder(self.root()):
yield p
def _subtree_postorder(self, p):
"""
针对根结点在位置p处的子树,产生关于子树所有结点位置的一个后序迭代
:param p: 结点位置
:return:
"""
for child in self.children(p):
for each in self._subtree_postorder(child): # 对child的子树做后序遍历
yield each # 懒惰式生成结点位置给生成器调用者
yield p # 先遍历以位置p处结点为根结点的子树,再访问位置p处结点
def positions(self):
"""生成一个树的结点位置的迭代"""
return self.preorder()
def __iter__(self):
"""生成一个树的结点元素的迭代"""
for p in self.positions():
yield p.element()
def is_root(self, p):
"""如果位置p处的结点为根结点则返回True"""
return self.root() == p
def is_leaf(self, p):
"""如果位置p处的结点无任何子结点则返回True"""
return self.num_children(p) == 0
def is_empty(self):
"""如果树为空,则返回True"""
return len(self) == 0
def depth(self, p):
"""
返回位置p处结点的深度
:param p: 结点位置
:return: 结点深度
"""
if self.is_root(p):
return 0
else:
return 1 + self.depth(self.parent(p))
def _height(self, p):
"""
返回位置p处结点的高度
:param p: 结点位置
:return: 结点高度
"""
if self.is_leaf(p):
return 0
else:
return 1 + max(self._height(child) for child in self.children(p))
def height(self, p=None):
"""
返回位置p处结点的高度,默认返回根结点高度
:param p: 结点位置
:return: 结点高度
"""
if p is None:
p = self.root()
return self._height(p)
class BinaryTree(Tree, metaclass=ABCMeta):
"""表示二叉树的抽象基类"""
@abstractmethod
def left(self, p):
"""
返回位置p处结点的左子结点,如该处结点无左子结点则返回None
:param p: 结点位置
:return: 结点位置或None
"""
@abstractmethod
def right(self, p):
"""
返回位置p处结点的右子结点,如该处结点无右子结点则返回None
:param p: 结点位置
:return: 结点位置或None
"""
def sibling(self, p):
"""
返回位置p处结点的兄弟结点,如该处结点无兄弟结点则返回None
:param p: 结点位置
:return: 结点位置或None
"""
parent = self.parent(p)
if parent is None: # 如果位置p处为根结点,则该位置处的结点无兄弟结点
return None
else:
if p == self.left(parent): # 如果位置p处是左结点,则返回右结点位置(可能无)
return self.right(parent)
else: # 如果位置p处是右结点,则返回左结点位置(可能无)
return self.left(parent)
def children(self, p):
"""
生成位置p处结点的子结点迭代
:param p: 结点位置
:return: 结点位置迭代
"""
if self.left(p) is not None:
yield self.left(p)
if self.right(p) is not None:
yield self.right(p)
def inorder(self):
"""
产生二叉树的一个中序遍历得到的结点位置迭代
:return:
"""
if not self.is_empty():
for p in self._subtree_inorder(self.root()):
yield p
def _subtree_inorder(self, p):
"""
针对根结点在位置p处的子二叉树,产生关于子二叉树树所有结点位置的一个中序迭代
:param p: 结点位置
:return:
"""
if self.left(p) is not None: # 如果位置p处结点存在左子结点,则遍历其左子树
for each in self._subtree_inorder(self.left(p)):
yield each
yield p # 在遍历位置p处结点的左子树之后,且在遍历右子树之前访问位置p处结点
if self.right(p) is not None: # 如果位置p处结点存在右子结点,则遍历其右子树
for each in self._subtree_inorder(self.right(p)):
yield each
class LinkedBinaryTree(BinaryTree):
"""采用链式结构实现的二叉树"""
class _Node: # 用于表示结点的类
__slots__ = 'element', 'parent', 'left', 'right'
def __init__(self, element, parent=None, left=None, right=None):
self.element = element
self.parent = parent
self.left = left
self.right = right
class Position(BinaryTree.Position):
"""用于描述结点位置的类"""
def __init__(self, container, node):
self._container = container
self._node = node
def element(self):
"""返回保存在该位置处的对象元素"""
return self._node.element
def __eq__(self, other):
"""当self和other表示同一个结点的位置时,返回True"""
return type(self) is type(other) and other._node is self._node
def __ne__(self, other):
"""当self和other表示不同结点的位置时返回True"""
return not (self == other)
def _pos2node(self, p: Position):
"""如果位置p有效,返回该位置处的结点引用"""
if not isinstance(p, self.Position):
raise TypeError('位置p:', p, '必须为准确的Position类型!')
if p._container is not self:
raise ValueError('位置p', p, '不属于当前位置列表!')
if p._node.parent is None and p._node.element is None:
raise ValueError('位置p', p, '已失效!')
return p._node
def _node2pos(self, node):
"""根据给定结点,创建并返回位置引用,当结点为None时返回None"""
return self.Position(self, node) if node is not None else None
def __init__(self):
"""创建一个空的二叉树"""
self._root = None
self._size = 0
def __len__(self):
"""返回二叉树实例中结点个数"""
return self._size
def root(self):
"""返回代表二叉树根结点位置的对象,当二叉树为空时返回None"""
return self._node2pos(self._root)
def parent(self, p):
"""返回代表位置p处结点的父结点的位置对象"""
node = self._pos2node(p)
return self._node2pos(node.parent)
def left(self, p):
"""返回代表位置p处结点的左子结点的位置对象,如无左子结点则返回None"""
node = self._pos2node(p)
return self._node2pos(node.left)
def right(self, p):
"""返回代表位置p处结点的右子结点的位置对象,如无右子结点则返回None"""
node = self._pos2node(p)
return self._node2pos(node.right)
def num_children(self, p):
"""返回位置p处结点的子结点数量"""
node = self._pos2node(p)
count = 0
if node.left is not None:
count += 1
if node.right is not None:
count += 1
return count
def positions(self, traversal_algorithm='inorder'):
if traversal_algorithm == 'inorder':
return self.inorder()
if traversal_algorithm == 'preorder':
return self.preorder()
if traversal_algorithm == 'postorder':
return self.postorder()
def _add_root(self, e):
"""如果二叉树为空则将元素e封装为结点后置为根结点并返回结点位置,否则抛出ValueError异常"""
if self._root is not None:
raise ValueError('此二叉树非空!')
self._size = 1
self._root = self._Node(e)
return self._node2pos(self._root)
def _add_left(self, p, e):
"""
为位置p处的结点创建保存元素e的左子结点并返回结点位置,
如果位置p处结点已有左子结点则抛出ValueError异常
"""
node = self._pos2node(p)
if node.left is not None:
raise ValueError('位置', p, '处已存在左子结点!')
self._size += 1
node.left = self._Node(e, parent=node) # 左子结点的父结点是node
return self._node2pos(node.left)
def _add_right(self, p, e):
"""
为位置p处的结点创建保存元素e的右子结点并返回结点位置,
如果位置p处结点已有右子结点则抛出ValueError异常
"""
node = self._pos2node(p)
if node.right is not None:
raise ValueError('位置', p, '处已存在左子结点!')
self._size += 1
node.right = self._Node(e, parent=node) # 右子结点的父结点是node
return self._node2pos(node.right)
def _replace(self, p, e):
"""替换位置p处结点的元素为e,并返回旧元素"""
node = self._pos2node(p)
old = node.element
node.element = e
return old
def _delete(self, p):
"""
删除位置p处结点并返回结点的对象元素,如果该结点有且仅有一个子结点则使用子结点代替其位置,
如果该结点有两个子结点,则抛出异常
"""
node = self._pos2node(p)
if self.num_children(p) == 2:
raise ValueError('位置', p, "处有两个子结点!")
child = node.left if node.left else node.right # child可能为None
if child is not None:
child.parent = node.parent # 待删除结点的父结点成为其子结点的父结点
if node is self._root:
self._root = child # 待删除的结点如为根结点则起子结点成为新的根结点
else:
parent = node.parent
if node is parent.left:
parent.left = child
else:
parent.right = child
self._size -= 1
node.parent = None # 识别已删除结点的约定
node.element = None # 识别已删除结点的约定
return node.element
def _attach(self, p, t1, t2):
"""将同类型的二叉树t1和t2在本树位置p的叶子结点处分别链接成左右子树"""
node = self._pos2node(p)
if not self.is_leaf(p):
raise ValueError('位置p:', p, '处的结点必须为叶子结点!')
if not (type(t1) is type(self) and type(t2) is type(self)):
raise TypeError('t1:', t1, '和t2:', t2, '必须为', type(self))
self._size += (len(t1) + len(t2))
if not t1.is_empty(): # 将t1链接为位置p处结点的左子树
t1._root.parent = node
node.left = t1._root
t1._root = None # 链接后已不存在t1这个二叉树
t1._size = 0
if not t2.is_empty(): # 将t2链接为位置p处结点的右子树
t2._root.parent = node
node.right = t2._root
t2._root = None # 链接后已不存在t2这个二叉树
t2._size = 0
class TreeMap(LinkedBinaryTree, MapBase):
"""使用二叉搜索树实现的有序映射类"""
class Position(LinkedBinaryTree.Position):
"""用于表示二叉搜索树中节点位置的类"""
def key(self):
"""返回映射中某键值对的键"""
return self.element().key
def value(self):
"""返回映射中某键值对的值"""
return self.element().value
def _subtree_search(self, p, k):
"""针对根节点在位置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):
"""返回以位置p处节点为根节点的二叉搜索(子)树中“第一个”节点的位置"""
walk = p
while self.left(walk) is not None:
walk = self.left(walk)
return walk
def _subtree_last_position(self, p):
"""返回以位置p处节点为根节点的二叉搜索(子)树中“最后一个”节点的位置"""
walk = p
while self.right(walk) is not None:
walk = self.right(walk)
return walk
def first(self):
"""返回该二叉搜索树第一个节点的位置"""
return self._subtree_first_position(self.root()) if len(self) > 0 else None
def last(self):
"""返回该二叉搜索树最后一个节点的位置"""
return self._subtree_last_position(self.root()) if len(self) > 0 else None
def before(self, p):
"""返回中序遍历时,位置p的前一个位置,当位置p为第一个位置时返回None"""
self._pos2node(p) # 确保待操作位置的节点依然有效
if self.left(p):
return self._subtree_last_position(self.left(p))
else:
walk = p
ancestor = self.parent(walk)
while ancestor is not None and walk == self.left(ancestor):
walk = ancestor
ancestor = self.parent(walk)
return ancestor
def after(self, p):
"""返回中序遍历时,位置p的后一个位置,当位置p为最后一个位置时返回None"""
self._pos2node(p) # 确保待操作位置的节点依然有效
if self.right(p):
return self._subtree_first_position(self.right(p))
else:
walk = p
ancestor = self.parent(walk)
while ancestor is not None and walk == self.right(ancestor):
walk = ancestor
ancestor = self.parent(walk)
return ancestor
def find_position(self, k):
"""
如果有序映射非空,则当有序映射中存在键为k的键值对,
则返回该键值对所在节点在二叉搜索树中的位置,
不存在则返回最后到达的位置,否则返回None
"""
if self.is_empty():
return None
else:
return self._subtree_search(self.root(), k)
def find_min(self):
"""
如有序映射非空,则返回有序映射中键最小的键值对所在节点在二叉搜索树中的位置,
否则返回None
"""
if self.is_empty():
return None
else:
p = self.first()
return p.key(), p.value()
def find_ge(self, k):
"""查找并返回键不小于k的键值对,如不存在这样的键值对则返回None"""
if self.is_empty():
return None
else:
p = self.find_position(k)
if p.key() < k:
p = self.after(p)
return p.key(), p.value() if p is not None else None
def find_range(self, start, stop):
"""
迭代所有满足start <= key < stop的键值对(key,value),
且如果start为None,则迭代从最小的键开始,
如果stop为None,则迭代到最大的键结束。
"""
if not self.is_empty():
if start is None:
p = self.first()
else:
p = self.find_position(start)
if p.key() < start:
p = self.after(p)
while p is not None and (stop is None or p.key() < stop):
yield p.key(), p.value()
p = self.after(p)
def __getitem__(self, k):
"""返回有序映射中键k所对应的值,如键k不存在则抛出KeyError异常"""
if self.is_empty():
raise KeyError('Key Error: ' + repr(k))
else:
p = self._subtree_search(self.root(), k)
if k != p.key():
raise KeyError('Key Error: ' + repr(k))
return p.value()
def __setitem__(self, k, v):
"""向有序映射中插入键值对(k, v),当k存在时替换原有的值"""
if self.is_empty():
self._add_root(self._Item(k, v))
else:
p = self._subtree_search(self.root(), k)
if p.key() == k:
p.element().value = v
return
else:
item = self._Item(k, v)
if p.key() < k:
self._add_right(p, item)
else:
self._add_left(p, item)
def __iter__(self):
"""生成映射所有键的一个迭代"""
p = self.first()
while p is not None:
yield p.key()
p = self.after(p)
def delete(self, p):
"""删除给定位置处的节点"""
self._pos2node(p) # 确保待操作位置的节点依然有效
if self.left(p) and self.right(p): # 位置p处有两个子节点
replacement = self._subtree_last_position(self.left(p))
self._replace(p, replacement.element()) # 将位置p处节点的键值对进行替换
p = replacement
# 这时位置p处至多有一个子节点
self._delete(p)
def __delitem__(self, k):
"""删除键为k的节点,当键k不存在时抛出KeyError异常"""
if not self.is_empty():
p = self._subtree_search(self.root(), k)
if k == p.key():
self.delete(p)
return
raise KeyError('Key Error: ' + repr(k))
def test_tree_map():
m = TreeMap()
print(m) # {}
m['K'] = 2
print(m) # {'K': 2}
m['B'] = 4
print(m) # {'K': 2, 'B': 4}
m['U'] = 2
print(m) # {'K': 2, 'B': 4, 'U': 2}
m['V'] = 8
print(m) # {'K': 2, 'B': 4, 'U': 2, 'V': 8}
m['K'] = 9
for k, v in m.find_range('C', 'V'):
print('(' + repr(k) + ': ' + repr(v) + ')') # ('K': 9) ('U': 2)
print(m['B']) # 4
print(m.get('F')) # None
print(m.get('F', 5)) # 5
print(m.get('K', 5)) # 9
print(len(m)) # 4
del m['V']
print(m) # {'K': 9, 'B': 4, 'U': 2}
print(m.pop('K')) # 9
print(m) # {'B': 4, 'U': 2}
print(m.setdefault('B', 1)) # 4
print(m.setdefault('A', 1)) # 1
print(m) # {'B': 4, 'U': 2, 'A': 1}
if __name__ == '__main__':
test_tree_map()