2783 字
14 分钟
树学习笔记
参考资料
- 树基础(树的概念&直径&重心&最小生成树)by langqi99
- 【北京大学】数据结构与算法Python版
- 【红黑树】动画详解 & 性质 & 插入 & 删除 & 旋转 & 应用 & 附完整代码 | 数据结构与算法
树的定义
树(Tree)由若干节点(Node)和两两连接节点的边(Edge)组成。
递归定义: 树是空集;或由根节点及0个或多个子树组成,其中子树亦是树,每个子树的根到根节点具有边相连 。
节点(Node)是树的基本部分
每个节点具有名称,或“键值”,节点还可以保存额外数据项,数据项根据不同的应用而改变
边(Edge)是组成树的另一个基本部分
每条边恰好连接两个节点,表示节点之间具有关联,边具有出入方向;每个节点(除了根节点)恰有一条来自另一节点的入边;每个节点可以有多条连到其他节点的出边。
树的性质
- 其中一个节点被指定为根节点(Root)
- 每个节点(除了根节点)恰连接一条来自节点p的边,p是n的父节点
- 每个节点从根开始的路径是唯一的
- 如果每个节点最多有两个子节点,这样的树被称为二叉树
树的嵌套列表实现
Tree = [root,left,right]
def BinaryTree(r): #创建一个二叉树 return [r, [], []]
def insertLeft(root, newBranch): #插入左子树 t = root.pop(1) if len(t) > 1: root.insert(1, [newBranch, t, []]) else: root.insert(1, [newBranch, [], []]) return root
def insertRight(root, newBranch): #插入右子树 t = root.pop(2) if len(t) > 1: root.insert(2, [newBranch, [], t]) else: root.insert(2, [newBranch, [], []]) return root
def getRootVal(root): #获取根节点 return root[0]
def setRootVal(root, newVal): #设置根节点 root[0] = newVal
def getLeftChild(root): #获取左子树 return root[1]
def getRightChild(root): #获取右子树 return root[2]实例
root = BinaryTree('a')insertRight(root, 'c')insertLeft(root, 'b')insertRight(root, 'e')# 与实例 2 相同
left = getLeftChild(root) # b 子树right = getRightChild(root) # e 子树
insertLeft(left, 'x') # b 的左侧:空 → 挂 xinsertRight(left, 'y') # b 的右侧:空 → 挂 y
insertLeft(right, 'm') # e 的左侧:空 → 挂 m(c 仍在 e 的右侧)
c = getRightChild(right) # c 子树insertLeft(c, 'p') # c 左侧挂 pinsertRight(c, 'q') # c 右侧挂 qflowchart TB
a(("a"))
b(("b"))
e(("e"))
x(("x"))
y(("y"))
m(("m"))
c(("c"))
p(("p"))
q(("q"))
a --> b
a --> e
b --> x
b --> y
e --> m
e --> c
c --> p
c --> q
树的链表实现
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树的应用
表达式解析
实例

从图示过程可以发现,创建树的关键是对当前节点的跟踪
- 创建左右子树可以用insertLeft和insertRight方法
- 当前节点设定值可以用setRootVal方法
- 下降到左右子树可以用getLeftChild和getRightChild方法
但是上升到父节点没有方法支持
因此我们可以用一个栈来记录跟踪父节点
当前节点下降时,将下降前的节点push入栈; 当前节点上升时,上升到pop出栈的节点即可
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
import operatordef evaluate(parseTree): #计算解析树(递归) opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}
leftC = parseTree.getLeftChild() #缩小规模 rightC = parseTree.getRightChild()
if leftC and rightC: return opers[parseTree.getRootVal()](evaluate(leftC), evaluate(rightC)) #递归调用 else: return parseTree.getRootVal() #叶子节点直接返回值(基本结束条件) ''' # 后序遍历实现 res1 = None res2 = None if tree: res1 = evaluate(parseTree.getLeftChild()) res2 = evaluate(parseTree.getRightChild()) if res1 and res2: return opers[parseTree.getRootVal()](res1, res2) else: return parseTree.getRootVal() '''树的遍历
深度优先搜索 (DFS)
- 前序遍历
先访问根节点,再递归前序访问左子树,最后前序访问右子树

def preorder(tree): if tree != None: print(tree.getRootVal()) preorder(tree.getLeftChild()) preorder(tree.getRightChild())- 中序遍历 先递归中序访问左子树,再访问根节点,最后中序访问右子树
def inorder(tree): if tree != None: inorder(tree.getLeftChild()) print(tree.getRootVal()) inorder(tree.getRightChild())- 后序遍历 先递归后序访问左子树,再后序访问右子树,最后访问根节点
def postorder(tree): if tree != None: postorder(tree.getLeftChild()) postorder(tree.getRightChild()) print(tree.getRootVal())广度优先搜索 (BFS)
层序遍历
先访问根节点,再访问左子树,最后访问右子树
def levelorder(tree): if tree != None: print(tree.getRootVal()) levelorder(tree.getLeftChild()) levelorder(tree.getRightChild())优先队列和二叉堆
ADT BinaryHeap的操作定义如下
- BinaryHeap() 创建一个空二叉堆对象
- insert(k) 添加一个新key到堆中
- findMin() 返回堆中最小的元素
- delMin() 返回堆中最小的元素,并从堆中删除
- isEmpty() 返回堆是否为空
- size() 返回堆中key的个数
- buildHeap(list) 从一个key列表中创建一个堆
python实现
class BinaryHeap: 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二叉查找树(BST)
class BinarySearchTree: def __init__(self): self.root = None self.size = 0
def length(self): return self.size
def __len__(self): return self.size
def __iter__(self): return self.root.__iter__()
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 remove(self,currentNode): #删除节点 if currentNode.isLeaf(): # 1) 叶子节点 if currentNode.isRoot(): self.root = None elif currentNode.isLeftChild(): currentNode.parent.leftChild = None else: currentNode.parent.rightChild = None elif currentNode.hasBothChildren(): # 2) 两个子节点 succ = currentNode.findSuccessor() succ.spliceOut() currentNode.key = succ.key currentNode.payload = succ.payload else: # 3) 只有一个子节点 child = currentNode.leftChild if currentNode.hasLeftChild() else currentNode.rightChild if currentNode.isRoot(): self.root = child child.parent = None elif currentNode.isLeftChild(): currentNode.parent.leftChild = child child.parent = currentNode.parent else: currentNode.parent.rightChild = child child.parent = currentNode.parent
class TreeNode: 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
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 findSuccessor(self): #删除节点时找到后继节点 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 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.parentAVL树(平衡二叉查找树)
复杂度最差为O(log n)
实现方法: 在插入和删除节点时,通过旋转操作保持树的平衡
Mermaid:4 种失衡与旋转示意
- LL(左左):对失衡节点做一次右旋
flowchart TB
subgraph Before_LL["LL 失衡(插入到左子树的左侧)"]
A1(("A")) --> B1(("B"))
A1 --> Ar1(("T3"))
B1 --> C1(("C"))
B1 --> Br1(("T2"))
C1 --> Cl1(("T0"))
C1 --> Cr1(("T1"))
end
subgraph After_LL["右旋 A 后"]
B2(("B")) --> C2(("C"))
B2 --> A2(("A"))
C2 --> Cl2(("T0"))
C2 --> Cr2(("T1"))
A2 --> Ar2(("T3"))
A2 --> Al2(("T2"))
end
- RR(右右):对失衡节点做一次左旋
flowchart TB
subgraph Before_RR["RR 失衡(插入到右子树的右侧)"]
A1(("A")) --> Al1(("T0"))
A1 --> B1(("B"))
B1 --> Bl1(("T1"))
B1 --> C1(("C"))
C1 --> Cl1(("T2"))
C1 --> Cr1(("T3"))
end
subgraph After_RR["左旋 A 后"]
B2(("B")) --> A2(("A"))
B2 --> C2(("C"))
A2 --> Al2(("T0"))
A2 --> Ar2(("T1"))
C2 --> Cl2(("T2"))
C2 --> Cr2(("T3"))
end
- LR(左右):先对左子节点左旋,再对失衡节点右旋
flowchart TB
subgraph Before_LR["LR 失衡(插入到左子树的右侧)"]
A1(("A")) --> B1(("B"))
A1 --> Ar1(("T3"))
B1 --> Bl1(("T0"))
B1 --> C1(("C"))
C1 --> Cl1(("T1"))
C1 --> Cr1(("T2"))
end
subgraph After_LR["左旋 B,再右旋 A 后"]
C2(("C")) --> B2(("B"))
C2 --> A2(("A"))
B2 --> Bl2(("T0"))
B2 --> Br2(("T1"))
A2 --> Al2(("T2"))
A2 --> Ar2(("T3"))
end
- RL(右左):先对右子节点右旋,再对失衡节点左旋
flowchart TB
subgraph Before_RL["RL 失衡(插入到右子树的左侧)"]
A1(("A")) --> Al1(("T0"))
A1 --> B1(("B"))
B1 --> C1(("C"))
B1 --> Br1(("T3"))
C1 --> Cl1(("T1"))
C1 --> Cr1(("T2"))
end
subgraph After_RL["右旋 B,再左旋 A 后"]
C2(("C")) --> A2(("A"))
C2 --> B2(("B"))
A2 --> Al2(("T0"))
A2 --> Ar2(("T1"))
B2 --> Bl2(("T2"))
B2 --> Br2(("T3"))
end
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) self.updateBalance(currentNode.leftChild) else: if currentNode.hasRightChild(): self.__put(key,val,currentNode.rightChild) else: currentNode.rightChild = TreeNode(key,val,parent=currentNode) self.updateBalance(currentNode.rightChild)
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) #调整父节点因子
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)
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)红黑树(Red-Black Tree)
性质
- 每个节点要么是红色,要么是黑色
- 根节点是黑色
- 所有叶子(指空节点 )都是黑色
- 不允许出现两个相邻的红色节点(不能出现“红-红相连”)
- 从任一节点到其所有叶子节点(空节点)的路径上,黑色节点数相同
直观理解:红黑树允许“局部不平衡”,但通过黑高约束避免退化成链表。
由性质 4 与性质 5 可以推出:任一路径上红节点不会连续出现,最长路径不会超过最短路径的 2 倍,因此高度是O(log n)。
红黑树插入操作

红黑树删除操作

- AVL:更严格平衡,通常查找更快,但插删调整可能更多
- 红黑:更宽松平衡,插删更“省旋转/更稳定”,工程中常用(如很多语言库的有序映射/集合)
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时









