一、二叉树知识点概述
二叉树节点结构
class Node:
def __init__(self, value, left=None, right=None, parent=None):
self.value = value
self.left = left
self.right = right
self.parent = parent # 非必须
搜索二叉树
一棵树中的所有子树中, 所有的左孩子都比父节点小, 所有的右孩子都比父节点大
完全二叉树
1 任意节点有右孩子没有左孩子 return false
2 在不违背第一条件的情况下, 如果遇到左右孩子不全的情况下, 在加下来遇到的所有孩子都必须为 叶子节点
平衡二叉树
每一科子树的左树和右树的高度差不超过1
满二叉树
满足 结点数量为 2^树的高度 - 1 的二叉树
二、树, 队列, 堆栈的简单数据结构
class Node:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def add_left(self, l_node):
self.left = l_node
def add_right(self, r_node):
self.right = r_node
def change_value(self, val):
self.value = val
class Stack:
def __init__(self):
self.elems = []
def push(self, item):
self.elems.append(item)
def pop(self):
if not self.elems:
raise "堆栈已经空了"
return self.elems.pop()
def is_empty(self):
return self.elems == []
def top(self):
return self.elems[-1]
def size(self):
return len(self.elems)
class Queue:
def __init__(self):
self.elems = []
def pop(self):
if self.elems:
return self.elems.pop(0)
else:
raise "队列已经空了"
def push(self, item):
self.elems.append(item)
def size(self):
return len(self.elems)
def is_empty(self):
return self.elems == []
def top(self):
return self.elems[0]
三、二叉树问题汇总
class BinaryTreeQues:
def __init__(self, tree=None):
self.tree = tree
def pre(self):
"""
前序遍历 根左右
:return:
"""
s = Stack()
s.push(self.tree)
pre_arr = []
while s.size():
pre_arr.append(s.pop())
print(pre_arr[-1].value, end=" ")
if pre_arr[-1].left:
s.push(pre_arr[-1].left)
if pre_arr[-1].right:
s.push(pre_arr[-1].right)
print()
return pre_arr
def post(self):
"""
后序遍历 左右根
:return:
"""
# 注释代码为用堆栈存储遍历序列
post_arr = []
s1 = Stack()
# s2 = Stack()
s1.push(self.tree)
while s1.size():
# item = s1.pop()
# s2.push(item)
post_arr.append(s1.pop())
if post_arr[-1].right:
s1.push(post_arr[-1].right)
if post_arr[-1].left:
s1.push(post_arr[-1].left)
# while s2.size():
# print(s2.pop().value, end=" ")
# print()
return list(reversed(post_arr))
def mid(self):
"""
中序遍历 左根右
:return:
"""
mid_arr = []
def push_all_left(tree_node, stack: Stack):
"""
将当前根节点的所有左孩子节点放入堆栈
:param tree_node: 子树根节点
:param stack: 辅助堆栈
:return:
"""
while tree_node:
stack.push(tree_node)
tree_node = tree_node.left
s = Stack() # 初始化一个堆栈
push_all_left(self.tree, s) # 将树的根节点的所有左孩子放入堆栈
while s.size():
mid_arr.append(s.pop()) # 在堆栈中拿出栈顶节点
print(mid_arr[-1].value, end=" ")
if mid_arr[-1].right: # 如果有右孩子 则将右孩子节点为子树的根节点 并将该子树中的所有左孩子节点放入堆栈(包括自身)
push_all_left(mid_arr[-1].right, s)
print()
return mid_arr
@staticmethod
def mid_by_left(head):
"""
左神写的中序遍历算法
Java代码转翻
:param head: self.tree
:return:
"""
if head:
s = Stack()
while s.size() or head:
if head:
s.push(head)
head = head.left
else:
head = s.pop()
print(head.value, end=" ")
head = head.right
print()
return
def DFS(self):
"""
宽度优先遍历
层序遍历
:return:
"""
if not self.tree:
return
q = Queue()
q.push(self.tree)
while not q.is_empty():
item = q.pop()
print(item.value, end=" ")
if item.left:
q.push(item.left)
if item.right:
q.push(item.right)
print()
return
def DFS_with_level(self):
"""
获取每层都有哪些节点, 和拥有最大节点数量的层的节点数量
:return: 树每层的节点值, 树中有最大节点数量的层的节点数量
"""
if not self.tree: # 如果树不存在 则返回
return
nodes_dic = {} # 纪录每层的节点值(顺序为由左至右)
cur_level = level = 0 # 当前层数, 所在层数
q = Queue() # 辅助队列
max_nodes_index = -1 # 拥有最多节点的层数的节点数量
cur_nodes_index = 0 # 当前层的节点数量(暂时量)
q.push((self.tree, level)) # 将根节点放入辅助队列
while not q.is_empty():
item, level = q.pop() # 弹出节点, 节点所在层
''''''
'''区域内代码为计算拥有最大节点数量的层的节点数量, 如若不需要可以删除'''
if level == cur_level: # 如果当前层和所在层一致
cur_nodes_index += 1 # 当前层节点 +1
else:
cur_level = level # 将当前层置换为所在层
cur_nodes_index = 1 # 当前节点数量 置为 1
if cur_nodes_index > max_nodes_index: # 判断当前层节点数量是否大于了纪录的最大节点数
max_nodes_index = cur_nodes_index
''''''
if level in nodes_dic: # 是否为该层的第一个节点
nodes_dic[level].append(item.value) # 如果不是 直接扩充
else:
nodes_dic[level] = [item.value] # 如果是 新建列表并将当前节点添加到列表中
if item.left: # 如果有左孩子, 则将左孩子放入队列
q.push((item.left, level + 1))
if item.right: # 如果有右孩子, 则将右孩子放入队列
q.push((item.right, level + 1))
return nodes_dic, max_nodes_index
def DFS_get_max_nodes(self):
"""
拥有最大节点数量的层的节点数量
:return: 拥有最大节点数量的层的节点数量
"""
cur_level = level = 0
q = Queue()
max_nodes = 0
cur_node = 0
q.push((self.tree, level))
while not q.is_empty():
item, level = q.pop()
if cur_level != level:
cur_level = level
cur_node = 1
else:
cur_node += 1
max_nodes = cur_node if cur_node > max_nodes else max_nodes
if item.left:
q.push((item.left, level + 1))
if item.right:
q.push((item.right, level + 1))
return max_nodes
@staticmethod
def DFS_get_max_nodes_by_left(head):
"""
左神 Java代码转翻
拥有最大节点数量的层的节点数量
:return: 拥有最大节点数量的层的节点数量
"""
if not head:
return
q = Queue()
cur_end = head # 当前层数最后一个节点
next_end = None # 下一层最后一个节点
cur_nodes_index = 0 # 当前层的节点数量
max_nodes_index = 0 # 拥有最多节点的层的节点数量
q.push(head) # 放入根节点
while not q.is_empty():
item = q.pop() # 拿出队列中的第一个节点
cur_nodes_index += 1 # 当前层节点数量 + 1
if item.left: # 如果有左孩子
q.push(item.left) # 将左孩子放入队列
next_end = item.left # 下一层的最后一个节点置为放入队列中的最后一个节点
if item.right: # 同理
q.push(item.right)
next_end = item.right
if item == cur_end: # 如果当前节点为当前层的最后一个节点
# 比较当前层的节点数量是否比以往层的最大节点数量多 如果多的话则给 max_nodes 赋值
max_nodes_index = cur_nodes_index if max_nodes_index < cur_nodes_index else max_nodes_index
cur_nodes_index = 0 # 当前层的节点数量置零
cur_end = next_end # 当前层的最后节点为下一层的最后一个节点
return max_nodes_index
def isBST(self):
"""
Binary Search Tree
是否为搜索二叉树
方法: 中序遍历, 将原本的打印操作变为比较操作
:return:
"""
min_value = -1
def push_all_left(tree_node, stack: Stack):
while tree_node:
stack.push(tree_node)
tree_node = tree_node.left
s = Stack() # 初始化一个堆栈
push_all_left(self.tree, s) # 将树的根节点的所有左孩子放入堆栈
while s.size():
item = s.pop() # 在堆栈中拿出栈顶节点
'''核心代码'''
if item.value <= min_value:
return False
else:
min_value = item.value
'''核心代码结束'''
if item.right: # 如果有右孩子 则将右孩子节点为子树的根节点 并将该子树中的所有左孩子节点放入堆栈(包括自身)
push_all_left(item.right, s)
return True
def isCBT(self):
"""
Complete Binary Tree
是否为完全二叉树
方法: 层序遍历
1 任意节点有右孩子没有左孩子 return false
2 在不违背第一条件的情况下, 如果遇到左右孩子不全的情况下,
在加下来遇到的所有孩子都必须为 叶子节点
:return:
"""
if not self.tree:
return True
q = Queue()
q.push(self.tree)
flag = 0 # 0: 没有左右孩子 1: 有左孩子 2: 有右孩子 3: 有左右孩子 -1: 从此节点开始之后应全为叶子节点
while not q.is_empty():
item = q.pop()
if item.left:
if flag == -1:
return False
flag += 1
q.push(item.left)
if item.right:
if flag <= 0: # 0 or -1
return False
flag += 2
q.push(item.right)
print(f'({flag}, {item.value})', end=" ")
if -1 <= flag <= 1: # -1 or 0 or 1
flag = -1
elif flag:
flag = 0
return True
'''
二叉树解题套路(递归方法)(树形DP)
1 分解子问题(搜寻条件)
2 问询左右子树信息, 将所有可能性列出
3 根据获取到的信息 做判断
举例(是否我平衡二叉树)
1 左子树是否为平衡二叉树, 右子树是否为平衡二叉树
左侧的value_max < cur_node.value
右侧的value_min > cur_node.value
2 得到信息
3 判断
左子树为平衡二叉树
右子树是平衡二叉树
左右子树加上父节点是否为平衡二叉树
如果上述全部 满足, 则为平衡二叉树
'''
def is_BST_by_recursion(self):
"""
是否为搜索二叉树 ( 递归解法 )
:return:
"""
def process(node: Node):
if not node:
return True, None, None
l_is_bst, l_min, l_max = process(node.left)
r_is_bst, r_min, r_max = process(node.right)
is_bst = True
max_ = min_ = node.value
if l_min:
min_ = min(min_, l_min)
if r_min:
max_ = max(max_, r_max)
# 左子树存在, 左子树不是搜索二叉树或者 左侧的最大值大于等于了当前节点的值
# 右子树存在, 右子树不是搜索二叉树或者 左侧的最小值小于等于了当前节点的值
if (l_min and (not l_is_bst or l_max >= node.value)) \
or (r_min and (not r_is_bst or r_min <= node.value)):
is_bst = False # 该树不是搜索二叉树
return is_bst, min_, max_ # 返回结果
return process(self.tree)[0]
def is_balanced_by_recursion(self):
def process(node: Node):
if not node:
return True, 0
l_height, l_is_balanced = process(node.left) # 问询左子树是否为平衡二叉树
r_height, r_is_balanced = process(node.right) # 问询右子树是否为平衡二叉树
height = max(l_height, r_height) + 1 # 获取当前节点发散出去的树的高度
# 判断当前树是否为平衡二叉树
# 左树 右树 拼接后的树
is_balanced = l_is_balanced and r_is_balanced and abs(l_height - r_height) < 2
# 返回结果和当前树的高度
return height, is_balanced
return process(self.tree)[0]
def is_FBT_by_recursion(self):
"""
Full Binary Tree
是否为满二叉树 ( 递归解法 )
:return:
"""
def process(node: Node):
if not node:
return 0, 0
l_height, l_nodes_index = process(node.left)
r_height, r_nodes_index = process(node.right)
inner_height = max(l_height, r_height) + 1
inner_nodes_index = l_nodes_index + r_nodes_index + 1
return inner_height, inner_nodes_index
height, node_index = process(self.tree)
# 判断根据, 节点数量 等于 2^树的高度 - 1
return node_index == (1 << height) - 1
def lowest_common_ancestor(self, node_1, node_2):
'''
找到 node_1 和 node_2 的共同祖先
:param node_1:
:param node_2:
:return: 树的学院关系表, node_1 和 node_2 的共同祖先
'''
ancestor_dic = {self.tree: [self.tree]}
def generate_relational_table(node):
if not node: # 如果节点不存在返回
return
if node.left: # 如果存在左孩子
ancestor_dic[node.left] = ancestor_dic.get(node, []).copy() # 复制父节点的血缘关系
ancestor_dic[node.left].append(node.left) # 添加本节点
generate_relational_table(node.left) # 向下获取血缘关系
if node.right:
ancestor_dic[node.right] = ancestor_dic.get(node, []).copy()
ancestor_dic[node.right].append(node.right)
generate_relational_table(node.right)
generate_relational_table(self.tree) # 生成整棵树的血缘关系
def check_in():
flag = None
print("-----------------")
for i in reversed(ancestor_dic.get(node_1)): # 从下往上查血缘关系
print(i.value)
if i in ancestor_dic.get(node_2): # 如果查找到的血缘关系在node_2的血缘关系表中
flag = i # 获取共同祖先节点
break
print("-----------------")
return flag # 返回
return ancestor_dic, check_in()
def lowest_common_ancestor_by_left(self, cur_node, node_1, node_2):
'''
左神 Java 代码转翻
1 node_1 是 node_2 的祖先 或者反过来
2 node_1 和 node_2 是通过汇聚找到一个共同祖先
:param cur_node: 当前节点
:param node_1: 要寻找共同祖先节点1
:param node_2: 要寻找共同祖先节点2
:return: 1: 空, node_1, node_2
2: 共同祖先(如果不是根节点 会直接向上传递 直到最后返回)
3: 左节点为 None 返回右节点 否则返回左节点
'''
# 当前节点为空, 当前节点为节点1 或者当前节点为节点2
if not cur_node or cur_node == node_1 or cur_node == node_2:
return cur_node # 1
left_node = self.lowest_common_ancestor_by_left(cur_node.left, node_1, node_2)
right_node = self.lowest_common_ancestor_by_left(cur_node.rightm, node_1, node_2)
# 如果左右节点都不为空
if left_node and right_node:
# 则表示当前节点为 node_1, node_2的共同祖先
return cur_node # 2
return left_node if left_node else right_node # 3
@staticmethod
def find_successor_node(node):
"""
找 node 的后继节点
在二叉树的中序遍历中, node的下一个节点叫 node 的后继节点
:param node: 带有父节点指针的节点 且假定整棵树节点都有父节点的指针
:return: node的后继节点, 如果没有 返回 None
"""
if not node:
return
if rn := node.right:
# 如果该节点有右子树, 则右子树的最左节点为 node 的后继节点
while rn.left: rn = rn.left
return rn
else:
parent = node.parent
while parent and parent.right is node:
'''如果父节点不为 None 且该节点为父节点的右孩子
向上查找一直查找到某一层为该层父节点的左孩子
则此时的父节点即为 node 的后继节点
如果最后找到父节点为 None 即找到了根节点
则说明该节点为此树的最后一个节点 无后继节点 返回 None
'''
node = parent
parent = node.parent
return parent
def Serialization(self):
"""
序列化
:return:
"""
if not self.tree:
return
q = Queue()
q.push(self.tree)
dic = {self.tree.value: None}
while not q.is_empty():
item = q.pop()
if item.left:
dic[item.left.value] = f'{item.value}_l'
q.push(item.left)
if item.right:
dic[item.right.value] = f'{item.value}_r'
with open("test.txt", "w", encoding="utf8") as f:
json.dump(dic, f)
def deserialization(self, ser_dic):
'''
反序列化
:param ser_dic:存储树结构的字典
:return:
'''
node_list = {}
for key, val in ser_dic.items():
node_list[key] = Node(key)
if val:
value, loc = val.split("_")
if loc == "l":
node_list[value].add_left(node_list[key])
else:
node_list[value].add_right(node_list[key])
for key in ser_dic:
self.tree = node_list[key]
return self.tree
def origami_problem(self, n):
"""
折纸问题
:param n: 折了几次
:return:
"""
if n < 1:
return
root_node = Node(0)
n_level_nodes = [root_node]
last_level_nodes = []
for i in range(1, n + 1):
for node in last_level_nodes:
node.add_left(Node(0))
node.add_right(Node(1))
n_level_nodes.extend([node.left, node.right])
last_level_nodes = n_level_nodes.copy()
n_level_nodes.clear()
self.tree = root_node
return self.mid()
四、自定义打印函数
def print_nodes_value(nodes_arr):
'''
根据遍历序列输出
:param nodes_arr:
:return:
'''
if not nodes_arr:
return
for i in nodes_arr:
print(i.value, end=" ")
print()
def print_nodes_in_dic_value(nodes_dic):
for key, value in nodes_dic.items():
print(key.value, end=" ")
for val in value:
print(val.value, end=" ")
print()
print()
五、主函数
if __name__ == '__main__':
# tree_10 = Node(10)
# tree_9 = Node(9)
# tree_8 = Node(8)
# tree_6 = Node(6, )
# tree_1 = Node(1, )
# tree_4 = Node(4, )
# tree_7 = Node(7, tree_6, tree_8)
# tree_2 = Node(2, tree_1)
# tree_3 = Node(3, tree_2, tree_4)
# tree_5 = Node(5, tree_3, tree_7)
# b = BinaryTreeQues(tree_5)
# # 层序遍历
# b.DFS_with_level()
# # 获取拥有最多节点的层的节点数量
# res = b.DFS_get_max_nodes()
# res = b.DFS_get_max_nodes_by_left()
# # 中序遍历
# res = b.mid()
# print_nodes_value(res)
# # 是否为搜索二叉树
# res = b.isBST_by_recursion()
# # 是否为完全二叉树
# res = b.isCBT()
# # 是否为满二叉树
# res = b.is_FBT_by_recursion()
# print(res)
# # 查找共同祖先
# res, check_res = b.lowest_common_ancestor(tree_1, tree_4)
# print_nodes_in_dic_value(res)
# print(check_res.value)
# # 序列化和反序列化
# b.Serialization()
# with open("test.txt", "r", encoding="utf8") as f:
# dic = json.load(f)
b = BinaryTreeQues()
# res = b.deserialization(dic)
# b.DFS()
res = b.origami_problem(3)
print_nodes_value(res)
本文根据 左神课程编写(一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解_哔哩哔哩_bilibili)
其中代码部分 除标_by_left 其余均为博主自己编写(可能有些跟视频中的算法相重合)