二叉搜索樹BST(BinarySearchTree)
BST是一棵二叉樹有著如下特點(diǎn):
所有小于父節(jié)點(diǎn)的節(jié)點(diǎn)都在左子樹中沦零,大于父節(jié)點(diǎn)的節(jié)點(diǎn)都在右子樹中。
這里的比較是指key鍵之間的行為,這樣就可以將BST描述成是一種ADT MAP(映射抽象數(shù)據(jù)結(jié)構(gòu))敦第。
MAP類通用的數(shù)據(jù)類接口:
map(): 創(chuàng)建一個(gè)映射類,Python中用構(gòu)造函數(shù)__init__()來完成
put(key,value): 增加鍵值對(duì)
get(key): 給定key值獲取到對(duì)應(yīng)的value值
del: 刪除某個(gè)鍵值對(duì) 實(shí)現(xiàn) del map[key] 這樣的操作
len(): 返回項(xiàng)目數(shù)
in: 判斷是否位于該映射內(nèi),返回bool值
完成這個(gè)數(shù)據(jù)結(jié)構(gòu)需要兩個(gè)類BST與Node霹粥,其中BST的成員屬于Node類空镜,Node類用鏈接來完成對(duì)某個(gè)節(jié)點(diǎn)的描述张抄,接下來依次用BST完成這些接口的描述:
- 構(gòu)造BinarySearchTree類
class BinarySearchTree(object):
"""構(gòu)建二叉搜索樹"""
def __init__(self):
self.root = None #節(jié)點(diǎn)屬于TreeNode類
self.size = 0
2.構(gòu)建Node節(jié)點(diǎn)類,為了簡(jiǎn)化BST類的工作极谊,定義很多屬性與輔助方法域那。
class Node(object):
"""構(gòu)建節(jié)點(diǎn)類"""
def __init__(self, key, val, left=None, right=None, parent=None):
self.key=key #鍵
self.val=val #值
self.leftChild=left #左子節(jié)點(diǎn)
self.rightChild=right #右子節(jié)點(diǎn)
self.parent=parent #父節(jié)點(diǎn),便于回溯
# 是否有左子節(jié)點(diǎn)
def hasLeftChild(self):
return self.leftChild
# 是否有右子節(jié)點(diǎn)
def hasRightChild(self):
return self.rightChild
# 是否是左子節(jié)點(diǎn):原理是擁有父節(jié)點(diǎn)并且父節(jié)點(diǎn)的左子節(jié)點(diǎn)是自己
def isLeftChild(self):
return self.parent and self.parent.leftChild == self
# 是否是右子節(jié)點(diǎn)
def isRightChild(self):
return self.parent and self.parent.rightChild == self
# 是否是根節(jié)點(diǎn):沒有父節(jié)點(diǎn)
def isRoot(self):
return not self.parent
# 是否是葉節(jié)點(diǎn):沒有子節(jié)點(diǎn)
def isLeaf(self):
return not(self.leftChild or self.rightChild)
# 是否含有子節(jié)點(diǎn)
def hasAnyChild(self):
return self.leftChild or self.rightChild
# 是否同時(shí)含有左右子節(jié)點(diǎn)
def hasBothChildren(self):
return self.leftChild and self.rightChild
# 替換節(jié)點(diǎn)數(shù)據(jù)
def replaceNodeData(self, key, val, left, right):
self.key = key
self.val = val
self.leftChild = left
self.rightChild = right
# 改變其左右子節(jié)點(diǎn)的指向鏈接
if self.hasLeftChild:
self.leftChild.parent = self
if self.rightChild:
self.rightChild.parent = self
3.返回BST的節(jié)點(diǎn)數(shù)
def length(self):
return self.size
# 可使用內(nèi)置函數(shù)len()返回節(jié)點(diǎn)數(shù)
def __len__(self):
return self.size
4.將BST變成一個(gè)迭代器,可用于for循環(huán)
# 迭代器
def __iter__(self):
return self.root.__iter__() #從根節(jié)點(diǎn)開始
5.在一棵二叉搜索樹中插入新節(jié)點(diǎn) put(),分兩種情況:
- 要插入的二叉樹為空被丧,將自己作為根節(jié)點(diǎn)
- 非空,調(diào)用遞歸函數(shù)
_put()
def put(self, key, val):
if self.root:
self._put(key, val, self.root)
else:
self.root = Node(key, val)
self.size += 1
6._put()
函數(shù)的過程描述:
-
如果要插入的key值<當(dāng)前節(jié)點(diǎn)的key值,那么就遞歸到其左子樹。
- 如果沒有左子樹,那么key就成為左子節(jié)點(diǎn)
-
如果要插入的key值>當(dāng)前節(jié)點(diǎn)的key值,那么就遞歸到其右子樹。
- 如果沒有右子樹,那么key就成為右子節(jié)點(diǎn)
如果要插入的key值=當(dāng)前節(jié)點(diǎn)的key值,那么就用新值替換掉舊值
根據(jù)流程描述寫出代碼:
def _put(self, key, val, currentNode):
# 如果key<當(dāng)前節(jié)點(diǎn)key值,遞歸到左子樹
if key < currentNode.key:
if currentNode.hasLeftChild:
self._put(key, val, current.leftChild)
else:
currentNode.leftChild = Node(key, val, parent=currentNode)
# 如果key>當(dāng)前節(jié)點(diǎn)key值,遞歸到右子樹
elif key > currentNode.key:
if currentNode.hasRightChild:
self._put(key, val, currentNode.rightChild)
else:
currentNode.rightChild = Node(key, val, parent=currentNode)
# 如果要插入的key值=當(dāng)前節(jié)點(diǎn)的key值覆致,那么就用新值替換掉舊值
else:get()方法:通過key值返回其對(duì)應(yīng)的val值宣羊,分幾種情況:
- 如果根節(jié)點(diǎn)為空族操,返回空(二叉樹為空)
- 如果root不為空,調(diào)用遞歸方法`_get()`尋找
- 如果找到,返回對(duì)應(yīng)val值
- 如果找不到,返回None或其他提示信息
self.currentNode.val = val
7.get()方法:通過key值返回其對(duì)應(yīng)的val值辜限,分幾種情況:
- 如果根節(jié)點(diǎn)為空毫深,返回空(二叉樹為空)
- 如果root不為空闸迷,調(diào)用遞歸方法
_get()
尋找- 如果找到,返回對(duì)應(yīng)val值
- 如果找不到墓臭,返回None或其他提示信息
# 查找功能
def get(self, key):
if self.root:
resNode = self._get(key, self.root)
if res:
return resNode.val
else:
return None
else:
return None
8.遞歸函數(shù)_get()
的邏輯流程與_put()
方法類似:
- 如果要比對(duì)的當(dāng)前節(jié)點(diǎn)為空,返回None
- 如果key值=當(dāng)前節(jié)點(diǎn)key值窖维,返回當(dāng)前節(jié)點(diǎn)
- 如果key值>當(dāng)前節(jié)點(diǎn)key值榆综,遞歸到右子樹
- 如果key值<當(dāng)前節(jié)點(diǎn)key值,遞歸到左子樹
# 私有遞歸
def _get(self, key, currentNode):
if not currentNode:
return None
elif key == currentNode.key:
return currentNode
elif key > currentNode.key:
self._get(key, currentNode.rightChild)
else:
self._get(key, currentNode.leftChild)
9.以上已經(jīng)實(shí)現(xiàn)二叉搜索樹的“增铸史、改鼻疮、查”,還有就是最重要的“刪”,比較啰嗦琳轿,大致分為以下幾種情況:
- 如果BST為空判沟,拋出一個(gè)異常
- 如果BST不為空,且只有一個(gè)節(jié)點(diǎn)root崭篡,刪除root
- 如果BST不為空挪哄,調(diào)用
_get()
方法,找到符合條件的Node節(jié)點(diǎn)琉闪,再調(diào)用remove()方法刪除
# 刪除節(jié)點(diǎn)
def delete(self, key):
if self.size > 1:
resNode = self._get(key, self.root)
if resNode:
self.remove(resNode)
self.size -= 1
else:
raise KeyError('查無此數(shù)迹炼!')
elif self.size == 1 and key == self.root.key:
self.root = None
self.size -= 1
else:
raise KeyError("數(shù)據(jù)為空!")
10.remove()方法分為三種情況颠毙,邏輯流程如下:
- 要?jiǎng)h除的節(jié)點(diǎn)無子節(jié)點(diǎn)斯入,也就是葉節(jié)點(diǎn),移出父節(jié)點(diǎn)對(duì)其的指向即可(區(qū)分左右兩種情況)
def remove(self, currentNode):
# 1 待刪除節(jié)點(diǎn)為葉節(jié)點(diǎn)蛀蜜,無子節(jié)點(diǎn)
if self.currentNode.isLeaf():
if currentNode == currentNode.parent.leftChild:
currentNode.parent.leftChild = None
else:
currentNode.parent.rightChild = None
2.要?jiǎng)h除的節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn)(左或右)刻两,分三種情況:
- 待刪除節(jié)點(diǎn)無父節(jié)點(diǎn),就是根節(jié)點(diǎn)了滴某,調(diào)用repalceNodeData()方法完成置換
- 待刪除節(jié)點(diǎn)為左子節(jié)點(diǎn):
- 待刪除節(jié)點(diǎn)有一個(gè)左子節(jié)點(diǎn)磅摹,將其左子節(jié)點(diǎn)上移,將待刪節(jié)點(diǎn)的父節(jié)點(diǎn)指向其左子節(jié)點(diǎn)的父節(jié)點(diǎn)霎奢,將待刪節(jié)點(diǎn)的子節(jié)點(diǎn)指向其父節(jié)點(diǎn)的左子節(jié)點(diǎn)偏瓤,跳過待刪節(jié)點(diǎn)。
- 待刪除節(jié)點(diǎn)有一個(gè)右子節(jié)點(diǎn)椰憋,左子節(jié)點(diǎn)上移厅克,將待刪節(jié)點(diǎn)的父節(jié)點(diǎn)指向其左子節(jié)點(diǎn)的父節(jié)點(diǎn),將待刪節(jié)點(diǎn)的左子節(jié)點(diǎn)指向其父節(jié)點(diǎn)的右子節(jié)點(diǎn)橙依,跳過待刪節(jié)點(diǎn)证舟。
- 待刪除節(jié)點(diǎn)為右子節(jié)點(diǎn)
- 待刪除節(jié)點(diǎn)有一個(gè)左子節(jié)點(diǎn)硕旗,與上同
- 待刪除節(jié)點(diǎn)有一個(gè)右子節(jié)點(diǎn),與上同
4.小結(jié)一下:刪除哪個(gè)節(jié)點(diǎn)女责,就是將其左或右子節(jié)點(diǎn)的引用以及其父節(jié)點(diǎn)指向該節(jié)點(diǎn)的引用更換漆枚,跳過該節(jié)點(diǎn)。
# 2 待刪除節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn)
else:
# 待刪除節(jié)點(diǎn)有左子節(jié)點(diǎn)
if currentNode.hasLeftChild():
if currentNode.isLeftChild():
# 左子節(jié)點(diǎn)上移抵知,將待刪節(jié)點(diǎn)的父節(jié)點(diǎn)指向其左子節(jié)點(diǎn)的父節(jié)點(diǎn)墙基,將待刪節(jié)點(diǎn)的左子節(jié)點(diǎn)指向其父節(jié)點(diǎn)的左子節(jié)點(diǎn)左电。
currentNode.leftChild.parent = currentNode.parent
currentNode.parent.leftChild = currentNode.leftChild
elif currentNode.isRightChild():
# 左子節(jié)點(diǎn)上移镶殷,將待刪節(jié)點(diǎn)的父節(jié)點(diǎn)指向其左子節(jié)點(diǎn)的父節(jié)點(diǎn)斟薇,將待刪節(jié)點(diǎn)的左子節(jié)點(diǎn)指向其父節(jié)點(diǎn)的右子節(jié)點(diǎn)孝冒。
currentNode.leftChild.parent = currentNode.parent
currentNode.parent.rightChild = currentNode.leftChild
else:
# 待刪節(jié)點(diǎn)是根節(jié)點(diǎn),將左子節(jié)點(diǎn)替換為根節(jié)點(diǎn)索赏,左子節(jié)點(diǎn)的右子節(jié)點(diǎn)作為更改后的左子節(jié)點(diǎn)
currentNode.replaceNodeData(currentNode.leftChild.key, currentNode.leftChild.val, left=currentNode.leftChild.rightChild)
# 待刪除節(jié)點(diǎn)有右子節(jié)點(diǎn)
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:
# 待刪節(jié)點(diǎn)是根節(jié)點(diǎn),將右子節(jié)點(diǎn)替換為根節(jié)點(diǎn)盲链,右子節(jié)點(diǎn)的右子節(jié)點(diǎn)作為更改后的右子節(jié)點(diǎn)壕吹,左子節(jié)點(diǎn)作為更改后的左子節(jié)點(diǎn)
currentNode.replaceNodeData(currentNode.rightChild.key, currentNode.rightChild.val,currentNode.rightChild.leftChild, currentNode.rightChild.rightChild)
- 要?jiǎng)h除的節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn)所踊,并不是簡(jiǎn)單的將其中一個(gè)子節(jié)點(diǎn)上移浊闪,這樣可能會(huì)破壞二叉搜索樹的關(guān)系恼布,所以必須找到一個(gè)合適的節(jié)點(diǎn)(后繼節(jié)點(diǎn)successor),在原位置刪除它搁宾,將其移動(dòng)的要?jiǎng)h除的節(jié)點(diǎn)的位置折汞。
# 3 待刪除節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn)
elif currentNode.hasBothChildren():
successor=self.findSuccessor()
successor.removeSucc()
currentNode.key=successor.key
currentNode.val=successor.val
-
尋找后繼節(jié)點(diǎn)(successor)findSuccesor():后繼節(jié)點(diǎn)就是待刪除節(jié)點(diǎn)右子樹中的最小節(jié)點(diǎn)
# 尋找后繼節(jié)點(diǎn) def findSuccessor(self): successor = None successor = self.rightChild.findMin() return successor
尋找最小節(jié)點(diǎn)findMin():在右子樹中找到最左邊的節(jié)點(diǎn)(自己想哈)
def findMin(self):
current = self
while current.hasLeftChild():
current = current.leftChild
return current
3.刪除后繼節(jié)點(diǎn)removeSucc(),要?jiǎng)h除的節(jié)點(diǎn)必定為左子節(jié)點(diǎn)!(自己想哈)盖腿,分為兩種情況:
- 要?jiǎng)h除的節(jié)點(diǎn)是葉節(jié)點(diǎn)字支,沒有子節(jié)點(diǎn)
- 要?jiǎng)h除的節(jié)點(diǎn)只有一個(gè)右節(jié)點(diǎn)(自己想哈)
# 刪除后繼節(jié)點(diǎn)
def removeSucc(self):
if self.isLeaf():
self.parent.leftChild = None
else:
self.parent.leftChild = self.rightChild
self.rightChild.parent = self.parent
11.其他內(nèi)置魔法方法,比較簡(jiǎn)單奸忽。
# 迭代器
def __iter__(self):
if self:
if self.hasLeftChild():
for elem in self.leftChild:
yield elem
yield self.key
if self.hasRightChild():
for elem in self.rightChild:
yield elem
# 賦值方法
def __setitem__(self, k, v):
self._put(k, v)
# 是否包含某個(gè)元素
def __contains__(self, k):
return True if self._get(k, self.root) else False
12.BST性能分析:
- 插入put()時(shí)間復(fù)雜度取決于樹的高度堕伪,如果BST是一棵平衡二叉樹(AVL),時(shí)間復(fù)雜度為O(logn);如果不是栗菜,比如該樹全為左子節(jié)點(diǎn)欠雌,時(shí)間復(fù)雜度為O(n)了。
13.BST全部參考代碼:
class BinarySearchTree(object):
"""構(gòu)建二叉搜索樹"""
def __init__(self):
self.root = None
self.size = 0
# 返回節(jié)點(diǎn)數(shù)
def length(self):
return self.size
# 可使用內(nèi)置函數(shù)len()返回節(jié)點(diǎn)數(shù)
def __len__(self):
return self.size
# 迭代器
def __iter__(self):
if self:
if self.hasLeftChild():
for elem in self.leftChild:
yield elem
yield self.key
if self.hasRightChild():
for elem in self.rightChild:
yield elem
# 賦值方法
def __setitem__(self, k, v):
self._put(k, v)
# 是否包含某個(gè)元素
def __contains__(self, k):
return True if self._get(k, self.root) else False
# 插入新節(jié)點(diǎn)
def put(self, key, val):
if self.root:
self._put(key, val, self.root)
else:
self.root = Node(key, val)
self.size += 1
#_put()函數(shù)流程
def _put(self, key, val, currentNode):
# 如果key>當(dāng)前節(jié)點(diǎn)key值疙筹,遞歸到左子樹
if key < currentNode.key:
if currentNode.hasLeftChild:
self._put(key, val, current.leftChild)
else:
currentNode.leftChild = Node(key, val, parent=currentNode)
# 如果key<當(dāng)前節(jié)點(diǎn)key值富俄,遞歸到右子樹
elif key > currentNode.key:
if currentNode.hasRightChild:
self._put(key, val, currentNode.rightChild)
else:
currentNode.rightChild = Node(key, val, parent=currentNode)
# 如果要插入的key值=當(dāng)前節(jié)點(diǎn)的key值,那么就用新值替換掉舊值
else:
self.currentNode.val = val
# 查找功能
def get(self, key):
if self.root:
resNode = self._get(key, self.root)
if res:
return resNode.val
else:
return None
else:
return None
# 私有遞歸
def _get(self, key, currentNode):
if not currentNode:
return None
elif key == currentNode.key:
return currentNode
elif key > currentNode.key:
self._get(key, currentNode.rightChild)
else:
self._get(key, currentNode.leftChild)
# 刪除節(jié)點(diǎn)
def delete(self, key):
if self.size > 1:
resNode = self._get(key, self.root)
if resNode:
self.remove(resNode)
self.size -= 1
else:
raise KeyError('查無此數(shù)而咆!')
elif self.size == 1 and key == self.root.key:
self.root = None
self.size -= 1
else:
raise KeyError("數(shù)據(jù)為空霍比!")
# 尋找最小值
def findMin(self):
current = self
while current.hasLeftChild():
current = current.leftChild
return current
# 尋找后繼節(jié)點(diǎn)
def findSuccessor(self):
successor = None
successor = self.rightChild.findMin()
return successor
# 刪除后繼節(jié)點(diǎn)
def removeSucc(self):
if self.isLeaf():
self.parent.leftChild = None
else:
self.parent.leftChild = self.rightChild
self.rightChild.parent = self.parent
# remove方法分三種情況
def remove(self, currentNode):
# 1 待刪除節(jié)點(diǎn)為葉節(jié)點(diǎn),無子節(jié)點(diǎn)
if self.currentNode.isLeaf():
if currentNode == currentNode.parent.leftChild:
currentNode.parent.leftChild = None
else:
currentNode.parent.rightChild = None
# 3 待刪除節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn)
elif currentNode.hasBothChildren():
successor = self.findSuccessor()
successor.removeSucc()
currentNode.key = successor.key
currentNode.val = successor.val
# 2 待刪除節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn)
else:
# 待刪除節(jié)點(diǎn)有左子節(jié)點(diǎn)
if currentNode.hasLeftChild():
if currentNode.isLeftChild():
# 左子節(jié)點(diǎn)上移暴备,將待刪節(jié)點(diǎn)的父節(jié)點(diǎn)指向其左子節(jié)點(diǎn)的父節(jié)點(diǎn)悠瞬,將待刪節(jié)點(diǎn)的左子節(jié)點(diǎn)指向其父節(jié)點(diǎn)的左子節(jié)點(diǎn)。
currentNode.leftChild.parent = currentNode.parent
currentNode.parent.leftChild = currentNode.leftChild
elif currentNode.isRightChild():
# 左子節(jié)點(diǎn)上移,將待刪節(jié)點(diǎn)的父節(jié)點(diǎn)指向其左子節(jié)點(diǎn)的父節(jié)點(diǎn)浅妆,將待刪節(jié)點(diǎn)的左子節(jié)點(diǎn)指向其父節(jié)點(diǎn)的右子節(jié)點(diǎn)望迎。
currentNode.leftChild.parent = currentNode.parent
currentNode.parent.rightChild = currentNode.leftChild
else:
# 待刪節(jié)點(diǎn)是根節(jié)點(diǎn),將左子節(jié)點(diǎn)替換為根節(jié)點(diǎn),左子節(jié)點(diǎn)的右子節(jié)點(diǎn)作為更改后的左子節(jié)點(diǎn)
currentNode.replaceNodeData(currentNode.leftChild.key, currentNode.leftChild.val, left=currentNode.leftChild.rightChild)
# 待刪除節(jié)點(diǎn)有右子節(jié)點(diǎn)
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:
# 待刪節(jié)點(diǎn)是根節(jié)點(diǎn),將右子節(jié)點(diǎn)替換為根節(jié)點(diǎn)凌外,右子節(jié)點(diǎn)的右子節(jié)點(diǎn)作為更改后的右子節(jié)點(diǎn)辩尊,左子節(jié)點(diǎn)作為更改后的左子節(jié)點(diǎn)
currentNode.replaceNodeData(currentNode.rightChild.key, currentNode.rightChild.val,currentNode.rightChild.leftChild, currentNode.rightChild.rightChild)