递归序:每次递归完,一个节点要经过3次
1 二叉树操作
在普通的二叉树中,插入新节点不需要保持任何排序规则,只需按照层级顺序依次插入到空闲的位置即可。
1.1 非递归完成先序中序后序遍历
遍历方法使用迭代的方式实现,包括中序遍历、前序遍历和后序遍历。采用了栈的辅助结构来模拟递归过程,实现非递归的遍历过程。
class Node:
def __init__(self,value):
self.value = value
self.left = None
self.right = None
class Binarytree: # 二叉树
def __init__(self):
self.root = None # 初始化头指针,指向空
def insert_node(self,value):
new_node = Node(value)
if not self.root: # 如果树为空
self.root = new_node
else: # 如果树不为空,queue只是表示有队列的意思,实际上还是列表
queue = [self.root] # 将头结点放入列表(队列)中,只加入头节点,用笔画画很好理解
while queue: # 头结点连着的元素
curr = queue.pop(0) # 将列表的首元素弹出
if not curr.left: # 如果弹出的这个结点左孩子是空
curr.left = new_node # 则添加左孩子
return
else:
queue.append(curr.left) # 如果有左孩子,就先放入列表中
if not curr.right:
curr.right = new_node
return
else:
queue.append(curr.right)
def inorder_traversal(self,root): # 中序遍历
if not root:
return []
result = []
stack = [] # 准备一个栈
curr = root # 引用头结点的指针
while curr or stack: # 如果指针指向元素或栈有元素,继续执行
while curr: # 指针指向元素
stack.append(curr) # 这不是真的栈,只是模仿栈的定义,加入curr指向的结点
curr = curr.left # curr指向左孩子
curr = stack.pop() # curr此时指向栈中弹出元素,pop弹出列表最后一个元素
result.append(curr.value)
curr = curr.right
return result
def preorder_traversa(self,root): # 先序遍历
if not root:
return []
result = []
stack = [root] # 将头结点转为列表,结点都在列表里
while stack: # 栈不为空
curr = stack.pop() # curr指向栈弹出元素
result.append(curr.value)
if curr.right:
stack.append(curr.right)
if curr.left:
stack.append(curr.left)
return result
def postorder_traversal(self,root): # 后序遍历
if not root:
return []
result = []
stack = [(root,False)]
while stack:
curr,visited = stack.pop() # 给curr,visited统一指向弹出的结点组合,curr指向结点,visited赋值False或True
if visited: # 若结点是True
result.append(curr.value) # 就加入到列表中
else:
stack.append((curr,True)) # 给当前结点改成True
if curr.right: # 结点的右孩子存在,放入栈中
stack.append((curr.right,False))
if curr.left: # 结点的左孩子存在,放入栈中
stack.append((curr.left,False))
return result
T = Binarytree()
T.insert_node(1)
T.insert_node(2)
T.insert_node(3)
print(T.postorder_traversal(T.root))
stack = [(root,False)]解释
在后序遍历中,对于任意一个节点,需要先遍历它的左子树和右子树,最后再访问该节点本身。为了满足这个顺序,使用栈作为辅助结构。初始时,将根节点和一个False标志位放入栈中。
后序遍历的算法如下:
1.当栈不为空时,从栈中弹出一个节点,并检查该节点的标志位。
2.如果标志位为True,表示该节点的右子树已经被访问过,可以将该节点的值加入结果列表中。
3.如果标志位为False,表示该节点的右子树还未被访问过,需要将该节点的右子树、左子树按照顺序依次放入栈中,并将该节点的标志位设为True。
重复步骤1到步骤3,直到栈为空。
1.2 删除为key值节点(递归)和找到后继节点
- 首先,找到要删除的节点。从根节点开始,在二叉树中递归搜索与要删除的节点值匹配的节点。
- 找到要删除的节点后,根据其子节点的情况执行不同的操作:
- 如果要删除的节点是叶子节点(没有左子节点和右子节点),直接将其从其父节点的左子树或右子树中删除。
- 如果要删除的节点只有一个子节点,将该子节点提升到要删除的节点的位置上。
- 如果要删除的节点有两个子节点,可以选择用该节点的前驱或后继节点来替代它,然后再递归删除用于替代的节点。
# 删除值为key的结点
def delete_node(self,root,key):
if not root:
return root
# 寻找要删除的结点
if root.value == key:
# 节点为叶子节点或只有一个子节点
if not root.left:
return root.right
elif not root.right:
return root.left
# 节点有两个子节点,用后继节点代替
successor = self.get_successor(root.right)
root.value = successor.value
root.right = self.delete_node(root.right,successor.value)
else:
# 继续在左子树和右子树中寻找要删除的节点
root.left = self.delete_node(root.left,key)
root.right = self.delete_node(root.right,key)
return root
def get_successor(self,node):
while node.left:
node = node.left
return node
1.4 直接删除节点(递归)
在函数中首先判断了树是否为空。如果树不为空,则递归地删除左子树和右子树中的最后一个节点,并通过将其父节点的对应子节点设为 None 来删除。
这个什么树都能用
def delete_last_node(self,root):
if not root:
return root
# 判断是否为叶子节点
if not root.left and not root.right: # 左右孩子都为空
return None
# 递归删除左子树和右子树中的最后一个节点
root.left = self.delete_last_node(root.left)
root.right = self.delete_last_node(root.right)
return root
1.5 直接删除节点(非递归)
- 首先,判断二叉树是否为空。如果为空,则无需进行删除操作。
- 如果不为空,定义一个栈(stack)用于存储要遍历的节点。将根节点压入栈中。
- 使用 while 循环,每次弹出栈顶元素,判断其是否为叶子节点。如果是叶子节点,则将其父节点的相应子节点设为 None。如果不是叶子节点,则将其左右子节点依次压入栈中。
- 循环直到栈为空为止。
这个什么树都能用
def delete_last_node(self, root):
if not root:
return root
stack = [root] # 以栈储存
while stack:
node = stack.pop() # 将首位节点弹出
if not node.left and not node.right:
# 如果节点为叶子节点,则删除其父节点对应的相应子节点
parent = self.find_parent(root,node) # 找到node的父节点
if parent:
if parent.left == node:
parent.left = None
else:
parent.right = None
else:
# 如果节点不是叶子节点,则将其左右子节点压入栈中
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
return root
# 找到node节点的父节点
def find_parent(self,root,node):
if not root or not node: # 头结点为空或当前节点为空
return None
if root == node: # 当前节点是父节点
return None
if root.left == node or root.right == node:
return root
left_parent = self.find_parent(root.left,node)
if left_parent:
return left_parent
right_parent = self.find_parent(root.right,node)
if right_parent:
return right_parent
return None
以上代码实现了 delete_last_node 函数,该函数定义了一个栈来实现循环遍历,并且使用 find_parent 函数来查找当前节点的父节点。在 while 循环中,首先弹出栈顶元素,并判断其是否为叶子节点,如果是叶子节点,则删除其父节点的相应子节点;否则,将其左右子节点压入栈中,以便后续遍历
1.6 实现二叉树按层遍历
-
宽度优先遍历,用队列
-
可以通过设置flag变量的方式,来发现某一层的结束
-
宽度优先遍历,用队列
# 二叉树按层遍历
def leve_traversal(self,root):
if not root:
return []
queue = [root] # 队列放头结点,列表用好了真不错
result = []
while queue:
cur = queue.pop(0)
result.append(cur.value)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
return result
- 可以通过设置flag变量的方式,来发现某一层的结束
哪一层的节点最多
方案一:用map
# 哪一层节点数量最多
def maxwidthMap(self,root):
if not root:
return 0
queue = [root]
# key在哪一层,value都存在map里
levelMap = dict()
levelMap.update({root:1}) # 头结点在第一层
curLevel = 1 # 当前你正在统计哪一层的宽度
curLevelNodes = 0 # 当前层curLevel层,宽度目前是多少,是0是因为一个节点出来后再加到宽度上去
max = 0
while queue:
cur = queue.pop(0) # 取第0个位置的数,模仿先进先出
curNodeLevel = levelMap.get(cur) # 取出当前节点层数
if cur.left: # 如果当前节点左孩子不为空
levelMap.update({cur.left:curNodeLevel+1}) # 将左孩子放入字典,对应层数+1
queue.append(cur.left) # 把左孩子放入队列中
if cur.right:
levelMap.update({cur.right:curNodeLevel+1}) #
queue.append(cur.right)
# 如果当前节点所在层数和目前统计层数一样,就是当前层环没有结束。如果不一样就是当前层结束了
if curNodeLevel == curLevel: # 一样,max不更新
curLevelNodes = curLevelNodes + 1 # 当前层的节点数++
else: # 有最新层才能更新max,最后一层没有更新max,新层到来结算老层
if max < curLevelNodes:
max = curLevelNodes
curLevel = curLevel + 1 # 当前层数++
curLevelNodes = 1 # 新层的节点数设置为1
if max < curLevelNodes: # 最后一层更新max机制
max = curLevelNodes
return max
方案2:用队列
# 哪一层节点数量最多
def maxwidthMap(self,root):
if not root:
return 0
queue = [root]
# key在哪一层,value都存在map里
curEnd = root # 当前层的最右节点是谁
nextEnd = None # 下一层的最右节点是谁
curLevelNodes = 0 # 当前层的节点数
max = 0
while queue:
cur = queue.pop(0) # 取第0个位置的数,模仿先进先出
if cur.left: # 如果当前节点左孩子不为空
queue.append(cur.left) # 把左孩子放入队列中
nextEnd = cur.left # nextEnd 总是往右动
if cur.right:
queue.append(cur.right)
nextEnd = cur.right
curLevelNodes = curLevelNodes + 1 # 当前层的节点数++
if cur == curEnd: # 如果当前节点是这一层最右的节点,就结算
if max < curLevelNodes:
max = curLevelNodes
curLevelNodes = 0 # 开始下一层的节点数
curEnd = nextEnd # 下一层的最右节点赋值给当前层节点,提前发现下一层的尾部
return max
1.7 二叉树的序列化和反序列化
序列化(serialization)是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。
# 二叉树先序序列化
def preSerial(self,root):
queue = []
self.pers(root,queue) # 把头结点和队列传过去
return queue
def pers(self,root,queue):
if not root:
queue.append(None) # 放空进去
else: # 和先序遍历差不多
queue.append(str(root.value)) # 将值变成字符串的形式放入队列中,调这个顺序变成中,后遍历
self.pers(root.left,queue)
self.pers(root.right,queue)
# 二叉树反序列化,将树再连好
def buildByPreQueue(self,prelist): # prelist是序列化后的列表
if prelist is None or len(prelist) == 0:
return None
return self.preb(prelist)
def preb(self,prelist): # 反先序序列化
value = prelist.pop(0)
if value is None:
return None # 建出空树返回
head = Node(value) # 将string类型的值转成节点类型,调这个顺序变成中,后遍历
head.left = self.preb(prelist)
head.right = self.preb(prelist)
return head
1.8 按层序列化
# 按层序列化,死活都要序列化,但只有不空才去加队列
def levelSerial(self,root):
ans = [] # ans放序列化后的结果
if not root:
ans.append(None)
else:
ans.append(str(root.value))
queue = [root] # queue以队列放节点
while queue:
root = queue.pop(0)
if root.left:
ans.append(str(root.left.value)) # 又序列化
queue.append(root.left) # 又队列
else:
ans.append(None) # 左孩子为空,只加序列,不加队列
if root.right:
ans.append(str(root.right.value))
queue.append(root.right)
else:
ans.append(None)
return ans
# 反水平序列化,节点为空也要建,死活要建节点,不空才加队列
def buildByLevelQueue(self,levelList):
if levelList is None or len(levelList) == 0:
return None
head = self.generateNode(levelList.pop(0))
queue = []
if head:
queue = [head]
while queue:
node = queue.pop(0)
node.left = self.generateNode(levelList.pop(0))
node.right = self.generateNode(levelList.pop(0))
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return head
def generateNode(self,value): # 给字符串,返回一个节点
if value is None:
return None
return Node(value)
T = Binarytree()
T.insert_node(1)
T.insert_node(2)
T.insert_node(3)
T.insert_node(4)
T.insert_node(5)
T.insert_node(6)
S = T.levelSerial(T.root)
N = T.buildByLevelQueue(S)
print(T.leve_traversal(N))
2 二叉排序树
对于插入节点,我们需要做的是遍历整个二叉树,找到恰当的位置将节点插入。具体实现中,如果当前节点的值小于待插入节点的值,则去左子树继续查找;否则去右子树查找。直到找到一个没有子节点的位置,将新节点插入到这个位置上。
2.1 递归完成前中后序遍历
# 二叉排序树
class TreeNode:
def __init__(self,value):
self.value = value
self.left = None
self.right = None
class BinaryTree:
def __init__(self):
self.root = None
def insert_node(self,value):
new_node = TreeNode(value)
if not self.root: # 若树没有任何值
self.root = new_node
else: # 如果树有结点
curr = self.root # 指针指向头结点
while True:
if value < curr.value: # 输入的值大于当前结点的值
if curr.left: # 如果做孩子存在
curr = curr.left # 指针指向做孩子
else: # 如果没有做孩子
curr.left = new_node # 当前结点的左指针指向新结点
break
elif value > curr.value: # 如果当前值大于当前结点
if curr.right: # 右孩子存在
curr = curr.right
else:
curr.right = new_node
break
def inorder_traversal(self, root): # 中序遍历
if not root:
return []
result = []
result += self.inorder_traversal(root.left)
result.append(root.value)
result += self.inorder_traversal(root.right)
return result
def preorder_traversal(self, root): # 前序遍历
if not root:
return []
result = []
result.append(root.value)
result += self.preorder_traversal(root.left)
result += self.preorder_traversal(root.right)
return result
def postorder_traversal(self, root): # 后序遍历
if not root:
return []
result = []
result += self.postorder_traversal(root.left)
result += self.postorder_traversal(root.right)
result.append(root.value)
return result
T = BinaryTree()
T.insert_node(1)
T.insert_node(2)
T.insert_node(3)
print(T.inorder_traversal(T.root))
非递归上面能用
2.2 删除值为key的节点
实现二叉树的删除节点操作:
- 找到需要删除的节点:从根节点开始,根据要删除的节点的值和当前节点的值进行比较,如果相等则找到了要删除的节点,否则根据比较结果继续向左子树或右子树进行搜索。
- 根据要删除的节点所处的情况进行不同的处理:
- 如果要删除的节点是叶子节点(没有左子节点和右子节点),直接删除即可。
- 如果要删除的节点只有一个子节点,将其子节点提升到要删除的节点的位置上。
- 如果要删除的节点有两个子节点,可以选择用该节点的前驱或后继节点来替代它,然后再递归删除用于替代的节点。
# 删除值为key的结点
def delete_node(self,root,key): # 所删除结点为key的值
if not root:
return root
if key < root.value:
root.left = self.delete_node(root.left,key)
elif key > root.value:
root.right = self.delete_node(root.right,key)
else:
# 结点为叶子结点或只有一个子节点
if not root.left:
return root.right
if not root.right:
return root.left
# 结点只有两个子节点,用后继节点替代
successor = self.get_successor(root.right)
root.value = successor.value
root.right = self.delete_node(root.right,successor.value)
return root
# 查找结点的后继结点
def get_successor(self,node):
while node.left:
node = node.left
return node
- 在上面的代码中,delete_node 方法接收一个参数 key,表示要删除的节点的值。如果树中存在该节点,则通过递归调用删除节点,并根据节点的情况进行相应的操作。
- get_successor 方法用于查找给定节点的后继节点。后继节点是右子树中的最小节点,它可以用来替代有两个子节点的节点