原目录
笔记
作者介绍了两个计算机中的树结构:文件系统(File System)和HTML页面。
用结点和引用实现的二叉树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| from __future__ import print_function
class BinaryTree: def __init__(self,rootObj): self.key = rootObj self.leftChild = None self.rightChild = None
def insertLeft(self,newNode): if self.leftChild == None: self.leftChild = BinaryTree(newNode) else: t = BinaryTree(newNode) t.leftChild = self.leftChild self.leftChild = t
def insertRight(self,newNode): if self.rightChild == None: self.rightChild = BinaryTree(newNode) else: t = BinaryTree(newNode) t.rightChild = self.rightChild self.rightChild = t
def getRightChild(self): return self.rightChild
def getLeftChild(self): return self.leftChild
def setRootVal(self,obj): self.key = obj
def getRootVal(self): return self.key
|
1 2 3 4 5 6 7 8 9 10 11
| r = BinaryTree('a') print(r.getRootVal()) print(r.getLeftChild()) r.insertLeft('b') print(r.getLeftChild()) print(r.getLeftChild().getRootVal()) r.insertRight('c') print(r.getRightChild()) print(r.getRightChild().getRootVal()) r.getRightChild().setRootVal('hello') print(r.getRightChild().getRootVal())
|
1 2 3 4 5 6 7
| a None <__main__.BinaryTree instance at 0x7f55e658a320> b <__main__.BinaryTree instance at 0x7f55e33bedd0> c hello
|
解析树
Parse Tree的构建规则(这里是数学表达式的解析树):
- 如果当前token是
'('
,增加一个结点作为左子结点,下降到左子结点。
- 如果当前token属于
['+','-','/','*']
,将当前结点的值设为这个操作符,增加一个结点作为右子结点,下降到右子结点。
- 如果当前token是一个数字,则将当前结点的值设为这个数字,回到父结点。
- 如果当前token是
')'
,回到父结点。
构建的解析树如下:
构建解析树的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| from pythonds.basic.stack import Stack from pythonds.trees.binaryTree import BinaryTree
def buildParseTree(fpexp): fplist = fpexp.split() pStack = Stack() eTree = BinaryTree('') pStack.push(eTree) currentTree = eTree for i in fplist: if i == '(': currentTree.insertLeft('') pStack.push(currentTree) currentTree = currentTree.getLeftChild() elif i not in ['+', '-', '*', '/', ')']: currentTree.setRootVal(int(i)) parent = pStack.pop() currentTree = parent elif i in ['+', '-', '*', '/']: currentTree.setRootVal(i) currentTree.insertRight('') pStack.push(currentTree) currentTree = currentTree.getRightChild() elif i == ')': currentTree = pStack.pop() else: raise ValueError return eTree
pt = buildParseTree("( 3 + ( 4 * 5 ) )") pt.postorder()
|
计算表达式的值:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import operator def evaluate(parseTree): opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}
leftC = parseTree.getLeftChild() rightC = parseTree.getRightChild()
if leftC and rightC: fn = opers[parseTree.getRootVal()] return fn(evaluate(leftC),evaluate(rightC)) else: return parseTree.getRootVal() evaluate(pt)
|
树的遍历
1.前序遍历(preorder),即根左右。
1 2 3 4 5
| def preorder(tree): if tree: print(tree.getRootVal()) preorder(tree.getLeftChild()) preorder(tree.getRightChild())
|
2.中序遍历(inorder),即左根右。
1 2 3 4 5 6
| def inorder(tree): if tree != None: inorder(tree.getLeftChild()) print(tree.getRootVal()) inorder(tree.getRightChild())
|
3.后序遍历(postorder),即左右根。
1 2 3 4 5
| def postorder(tree): if tree != None: postorder(tree.getLeftChild()) postorder(tree.getRightChild()) print(tree.getRootVal())
|
4.层次遍历,即:根/子子/孙孙孙孙
原书没有提及层次遍历,这里做一点补充。
1 2 3 4 5 6 7 8 9 10 11
| def level_order(tree): trav_queue = [] trav_queue.insert(0, tree) while len(trav_queue) != 0: cur_node = trav_queue.pop() if cur_node.getLeftChild(): trav_queue.insert(0, cur_node.getLeftChild()) if cur_node.getRightChild(): trav_queue.insert(0, cur_node.getRightChild()) print(cur_node.getRootVal())
|
1 2 3 4 5 6 7 8 9 10 11 12
| def preorder(tree): if tree: print(tree.getRootVal()) preorder(tree.getLeftChild()) preorder(tree.getRightChild()) r = BinaryTree('a') r.insertLeft('b') r.insertRight('c') r.getLeftChild().insertLeft('d') r.getLeftChild().insertLeft('e') preorder(r)
|
二叉堆
首先引入priority queue
的概念,你可以把它想象成操作系统中优先级队列,它的特征是:每一次出队的是优先级最高的元素。
如果你使用list实现它,每一次出队前需要重新排序,那么时间复杂度可能是$O(nlogn)$。或者你可以说,只需要第一次排个序就可以,后面每入队一个元素的时候,都加到合适的位置里去,这样插入一个元素的时间复杂度是$O(n)$。
计算机科学家觉得还是不够,于是发明了二叉堆
,使得优先级队列
的时间复杂度是$O(logn)$。
二叉堆从外观上来看是一棵完全二叉树,但它有一个性质,即父结点比子结点小(小顶堆)。
接下来看看如何实现一个二叉堆:
需要实现的方法:
实现的时候,首先牢记二叉堆的两个属性:
- The StructureProperty,即二叉堆在结构上是一棵完全二叉树。原因是完全二叉树可以直接存在list中。第0个元素置空,第$i$个元素的左子结点和右子结点分别是$i×2$和$i×2+1$,第i个元素的父结点是$i/2$。
- Heap Order Property,即父结点比子结点小。
主要关注两个过程,注意过程中要维护上面的两个属性:
- 插入元素时的percolate up,即结点比父结点大的,和父结点互换。
- 删除元素时的percolatedown,即删除顶结点以后,先将最后一个结点补入第一位,然后再不断地将这个结点跟其最小的子结点比,大于这个子结点则互换。
- 这里如果忘了,最好回去看原文,有很生动的图,一看就懂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| class BinHeap: def __init__(self): self.heapList = [0] self.currentSize = 0 def percUp(self,i): while i // 2 > 0: if self.heapList[i] < self.heapList[i // 2]: tmp = self.heapList[i // 2] self.heapList[i // 2] = self.heapList[i] self.heapList[i] = tmp i = i // 2 def insert(self,k): self.heapList.append(k) self.currentSize = self.currentSize + 1 self.percUp(self.currentSize) def percDown(self,i): while (i * 2) <= self.currentSize: mc = self.minChild(i) if self.heapList[i] > self.heapList[mc]: tmp = self.heapList[i] self.heapList[i] = self.heapList[mc] self.heapList[mc] = tmp i = mc
def minChild(self,i): if i * 2 + 1 > self.currentSize: return i * 2 else: if self.heapList[i*2] < self.heapList[i*2+1]: return i * 2 else: return i * 2 + 1 def delMin(self): retval = self.heapList[1] self.heapList[1] = self.heapList[self.currentSize] self.currentSize = self.currentSize - 1 self.heapList.pop() self.percDown(1) return retval def buildHeap(self,alist): i = len(alist) // 2 self.currentSize = len(alist) self.heapList = [0] + alist[:] while (i > 0): self.percDown(i) i = i - 1
|
二叉查找树
二叉查找树(bst)
即这样的二叉树:
所有的父结点的值都比左子结点的大,但比右子结点的小。
值得注意的是bst的删除操作,分三种情况:
1.该目标结点没有子节点;
这种情况直接删除目标结点即可。
2.该目标结点只有一个子节点;
这种情况需要先将目标结点删除,再把目标结点的子结点连到目标结点的父结点上
3.该目标结点有两个子节点。
这种情况最复杂,删除目标结点后,需要寻找successor,来替代当前结点的位置。
successor必须满足比目标结点所有左子树要大,比所有右子树要小,又分三种情况:
- 目标结点有右子结点,则successor是
右子树的最小结点
(这个结点是比目标结点大的最小结点)。上面图示就是这种情况。
- 目标结点没有右子结点,且是父结点的左子结点,这说明父结点小于目标结点的所有左子树,则successor直接选用目标结点的父结点。
- 目标结点没有右子结点,且是父结点的右子结点,这说明父结点大于目标结点的所有左子树,则递归地再去寻找父结点的successor。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
| class TreeNode: """helper类""" def __init__(self,key,val,left=None,right=None,parent=None): self.key = key self.payload = val self.leftChild = left self.rightChild = right self.parent = parent
def hasLeftChild(self): return self.leftChild
def hasRightChild(self): return self.rightChild
def isLeftChild(self): return self.parent and self.parent.leftChild == self
def isRightChild(self): return self.parent and self.parent.rightChild == self
def isRoot(self): return not self.parent
def isLeaf(self): return not (self.rightChild or self.leftChild)
def hasAnyChildren(self): return self.rightChild or self.leftChild
def hasBothChildren(self): return self.rightChild and self.leftChild
def replaceNodeData(self,key,value,lc,rc): self.key = key self.payload = value self.leftChild = lc self.rightChild = rc if self.hasLeftChild(): self.leftChild.parent = self if self.hasRightChild(): self.rightChild.parent = self
class BinarySearchTree:
def __init__(self): self.root = None self.size = 0
def length(self): return self.size
def __len__(self): return self.size
def put(self, key, val): if self.root: self._put(key, val, self.root) else: self.root = TreeNode(key,val) self.size = self.size + 1
def _put(self, key, val, currentNode): if key < currentNode.key: if currentNode.hasLeftChild(): self._put(key, val, currentNode.leftChild) else: currentNode.leftChild = TreeNode(key,val,parent=currentNode) else: if currentNode.hasRightChild(): self._put(key,val,currentNode.rightChild) else: currentNode.rightChild = TreeNode(key,val,parent=currentNode)
def __setitem__(self,k,v): self.put(k,v)
def get(self,key): if self.root: res = self._get(key,self.root) if res: return res.payload else: return None else: return None
def _get(self,key,currentNode): if not currentNode: return None elif currentNode.key == key: return currentNode elif key < currentNode.key: return self._get(key,currentNode.leftChild) else: return self._get(key,currentNode.rightChild)
def __getitem__(self, key): return self.get(key)
def __contains__(self, key): if self._get(key, self.root): return True else: return False
def delete(self, key): if self.size > 1: nodeToRemove = self._get(key, self.root) if nodeToRemove: self.remove(nodeToRemove) self.size = self.size-1 else: raise KeyError('Error, key not in tree') elif self.size == 1 and self.root.key == key: self.root = None self.size = self.size - 1 else: raise KeyError('Error, key not in tree')
def __delitem__(self, key): self.delete(key)
def spliceOut(self): if self.isLeaf(): if self.isLeftChild(): self.parent.leftChild = None else: self.parent.rightChild = None elif self.hasAnyChildren(): if self.hasLeftChild(): if self.isLeftChild(): self.parent.leftChild = self.leftChild else: self.parent.rightChild = self.leftChild self.leftChild.parent = self.parent else: if self.isLeftChild(): self.parent.leftChild = self.rightChild else: self.parent.rightChild = self.rightChild self.rightChild.parent = self.parent
def findSuccessor(self): """ 1.结点有右子结点,则successor是右子树的最小结点。 2.结点没有右子结点,且是父结点的左子结点,则successor是父结点。 3.结点没有右子结点,且是父结点的右子结点,则successor是父结点的successor。 """ succ = None if self.hasRightChild(): succ = self.rightChild.findMin() else: if self.parent: if self.isLeftChild(): succ = self.parent else: self.parent.rightChild = None succ = self.parent.findSuccessor() self.parent.rightChild = self return succ
def findMin(self): current = self while current.hasLeftChild(): current = current.leftChild return current
def remove(self,currentNode): """删除结点""" if currentNode.isLeaf(): if currentNode == currentNode.parent.leftChild: currentNode.parent.leftChild = None else: currentNode.parent.rightChild = None elif currentNode.hasBothChildren(): succ = currentNode.findSuccessor() succ.spliceOut() currentNode.key = succ.key currentNode.payload = succ.payload
else: if currentNode.hasLeftChild(): if currentNode.isLeftChild(): currentNode.leftChild.parent = currentNode.parent currentNode.parent.leftChild = currentNode.leftChild elif currentNode.isRightChild(): currentNode.leftChild.parent = currentNode.parent currentNode.parent.rightChild = currentNode.leftChild else: currentNode.replaceNodeData(currentNode.leftChild.key, currentNode.leftChild.payload, currentNode.leftChild.leftChild, currentNode.leftChild.rightChild) 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: currentNode.replaceNodeData(currentNode.rightChild.key, currentNode.rightChild.payload, currentNode.rightChild.leftChild, currentNode.rightChild.rightChild)
mytree = BinarySearchTree() mytree[3]="red" mytree[4]="blue" mytree[6]="yellow" mytree[2]="at"
print(mytree[6]) print(mytree[2])
|
平衡二叉查找树
当一般的二叉查找树出现倾斜的情况(skewed tree):
时间复杂度右重新变成了$O(n)$,于是引入平衡二叉查找树(Balanced Binary Search Trees, AVL tree),解决倾斜问题(AVL的命名源自其作者的名字)。
平衡因子(balance factor):$balanceFactor=height(leftSubTree)−height(rightSubTree)$,即左子树的高度-右子树的高度。
如果一棵二叉查找树可以被称为平衡,则其所有子树的平衡因子只能是:-1,0,1三者之一。
先来看看AVL的性能:
来找到最左倾的AVL的结点个数和高度:
很容易可以发现一个规律,即$N_h=1+N_{h−1}+N_{h−2}$。其中,$h$是树的高度,$N_h$是高度$h$的情况下,最左倾的AVL的结点个数。这个形态和斐波那契数列是一致的。斐波那契数列有一个性质就是,对于足够大的数,后一个比前一个的比值接近于黄金分割:$Φ=\frac{1+\sqrt5}{2}$。
可以将递推公式转换成:$h=1.44logN_h$,也就是说,AVL的查找操作的复杂度,依然是:$O(logN)$。
现在考虑如何维护平衡,对插入元素的父结点递归地更新平衡因子,直到:1.已经更新到root;2.当前父结点的平衡因子是0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def updateBalance(self,node): """更新平衡因子""" if node.balanceFactor > 1 or node.balanceFactor < -1: self.rebalance(node) return if node.parent != None: if node.isLeftChild(): node.parent.balanceFactor += 1 elif node.isRightChild(): node.parent.balanceFactor -= 1
if node.parent.balanceFactor != 0: self.updateBalance(node.parent)
|
获得新的平衡因子以后,需要对不平衡的结点做旋转(操作)。
左旋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| def rotateLeft(self,rotRoot): newRoot = rotRoot.rightChild rotRoot.rightChild = newRoot.leftChild if newRoot.leftChild != None: newRoot.leftChild.parent = rotRoot newRoot.parent = rotRoot.parent if rotRoot.isRoot(): self.root = newRoot else: if rotRoot.isLeftChild(): rotRoot.parent.leftChild = newRoot else: rotRoot.parent.rightChild = newRoot newRoot.leftChild = rotRoot rotRoot.parent = newRoot rotRoot.balanceFactor = rotRoot.balanceFactor + 1 - min(newRoot.balanceFactor, 0) newRoot.balanceFactor = newRoot.balanceFactor + 1 + max(rotRoot.balanceFactor, 0)
|
最后两行的更新平衡参数需要补充解释:
$newBal(B)=h_A−h_C$,
$oldBal(B)=h_A−h_D$,
对于旋转前:$h_D=1+max(h_C,h_E)$,而经过旋转后$h_C$和$h_E$是不变的。
于是有:$oldBal(B)=h_A−(1+max(h_C,h_E))$
再将两条表达式相减,有:
$$
newBal(B) - oldBal(B) = h_A - h_C - (h_A - (1 + max(h_C,h_E))) \
newBal(B) -
oldBal(B) = h_A - h_C - h_A + (1 + max(h_C,h_E)) \
newBal(B) - oldBal(B) = h_A
- h_A + 1 + max(h_C,h_E) - h_C \
newBal(B) - oldBal(B) = 1 + max(h_C,h_E) -
h_C \
newBal(B) = oldBal(B) + 1 + max(h_C - h_C ,h_E - h_C) \
newBal(B) =
oldBal(B) + 1 + max(0 , -oldBal(D)) \
newBal(B) = oldBal(B) + 1 - min(0 ,
oldBal(D)) \
$$
即
1 2
| rotRoot.balanceFactor = rotRoot.balanceFactor + 1 - min(newRoot.balanceFactor, 0)
|
类似的方法可以推出:
1 2
| newRoot.balanceFactor = newRoot.balanceFactor + 1 + max(rotRoot.balanceFactor, 0)
|
还有一个问题,如果是这样的右倾:
直接进行左旋,结果是:
结果变成了左倾。
因此在做旋转之前,比如左旋,先检查左子树是否左倾,如果有要先右旋左子树:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def rebalance(self,node): if node.balanceFactor < 0: if node.rightChild.balanceFactor > 0: self.rotateRight(node.rightChild) self.rotateLeft(node) else: self.rotateLeft(node) elif node.balanceFactor > 0: if node.leftChild.balanceFactor < 0: self.rotateLeft(node.leftChild) self.rotateRight(node) else: self.rotateRight(node)
|
讨论作业