本文主要是介绍[算法入土之路]二叉树,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、二叉树知识点概述
二叉树节点结构
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 = valueself.left = leftself.right = rightdef add_left(self, l_node):self.left = l_nodedef add_right(self, r_node):self.right = r_nodedef change_value(self, val):self.value = valclass 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 = treedef 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_arrdef 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.lefts = 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@staticmethoddef 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.leftelse:head = s.pop()print(head.value, end=" ")head = head.rightprint()returndef DFS(self):"""宽度优先遍历层序遍历:return:"""if not self.tree:returnq = 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()returndef DFS_with_level(self):"""获取每层都有哪些节点, 和拥有最大节点数量的层的节点数量:return: 树每层的节点值, 树中有最大节点数量的层的节点数量"""if not self.tree: # 如果树不存在 则返回returnnodes_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 # 当前层节点 +1else:cur_level = level # 将当前层置换为所在层cur_nodes_index = 1 # 当前节点数量 置为 1if 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_indexdef DFS_get_max_nodes(self):"""拥有最大节点数量的层的节点数量:return: 拥有最大节点数量的层的节点数量"""cur_level = level = 0q = Queue()max_nodes = 0cur_node = 0q.push((self.tree, level))while not q.is_empty():item, level = q.pop()if cur_level != level:cur_level = levelcur_node = 1else:cur_node += 1max_nodes = cur_node if cur_node > max_nodes else max_nodesif item.left:q.push((item.left, level + 1))if item.right:q.push((item.right, level + 1))return max_nodes@staticmethoddef DFS_get_max_nodes_by_left(head):"""左神 Java代码转翻拥有最大节点数量的层的节点数量:return: 拥有最大节点数量的层的节点数量"""if not head:returnq = 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 # 当前层节点数量 + 1if item.left: # 如果有左孩子q.push(item.left) # 将左孩子放入队列next_end = item.left # 下一层的最后一个节点置为放入队列中的最后一个节点if item.right: # 同理q.push(item.right)next_end = item.rightif item == cur_end: # 如果当前节点为当前层的最后一个节点# 比较当前层的节点数量是否比以往层的最大节点数量多 如果多的话则给 max_nodes 赋值max_nodes_index = cur_nodes_index if max_nodes_index < cur_nodes_index else max_nodes_indexcur_nodes_index = 0 # 当前层的节点数量置零cur_end = next_end # 当前层的最后节点为下一层的最后一个节点return max_nodes_indexdef isBST(self):"""Binary Search Tree是否为搜索二叉树方法: 中序遍历, 将原本的打印操作变为比较操作:return:"""min_value = -1def push_all_left(tree_node, stack: Stack):while tree_node:stack.push(tree_node)tree_node = tree_node.lefts = Stack() # 初始化一个堆栈push_all_left(self.tree, s) # 将树的根节点的所有左孩子放入堆栈while s.size():item = s.pop() # 在堆栈中拿出栈顶节点'''核心代码'''if item.value <= min_value:return Falseelse:min_value = item.value'''核心代码结束'''if item.right: # 如果有右孩子 则将右孩子节点为子树的根节点 并将该子树中的所有左孩子节点放入堆栈(包括自身)push_all_left(item.right, s)return Truedef isCBT(self):"""Complete Binary Tree是否为完全二叉树方法: 层序遍历1 任意节点有右孩子没有左孩子 return false2 在不违背第一条件的情况下, 如果遇到左右孩子不全的情况下,在加下来遇到的所有孩子都必须为 叶子节点:return:"""if not self.tree:return Trueq = 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 Falseflag += 1q.push(item.left)if item.right:if flag <= 0: # 0 or -1return Falseflag += 2q.push(item.right)print(f'({flag}, {item.value})', end=" ")if -1 <= flag <= 1: # -1 or 0 or 1flag = -1elif flag:flag = 0return True'''二叉树解题套路(递归方法)(树形DP)1 分解子问题(搜寻条件)2 问询左右子树信息, 将所有可能性列出3 根据获取到的信息 做判断举例(是否我平衡二叉树)1 左子树是否为平衡二叉树, 右子树是否为平衡二叉树左侧的value_max < cur_node.value右侧的value_min > cur_node.value2 得到信息3 判断 左子树为平衡二叉树右子树是平衡二叉树左右子树加上父节点是否为平衡二叉树如果上述全部 满足, 则为平衡二叉树'''def is_BST_by_recursion(self):"""是否为搜索二叉树 ( 递归解法 ):return:"""def process(node: Node):if not node:return True, None, Nonel_is_bst, l_min, l_max = process(node.left)r_is_bst, r_min, r_max = process(node.right)is_bst = Truemax_ = min_ = node.valueif 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, 0l_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_balancedreturn process(self.tree)[0]def is_FBT_by_recursion(self):"""Full Binary Tree是否为满二叉树 ( 递归解法 ):return:"""def process(node: Node):if not node:return 0, 0l_height, l_nodes_index = process(node.left)r_height, r_nodes_index = process(node.right)inner_height = max(l_height, r_height) + 1inner_nodes_index = l_nodes_index + r_nodes_index + 1return inner_height, inner_nodes_indexheight, node_index = process(self.tree)# 判断根据, 节点数量 等于 2^树的高度 - 1return node_index == (1 << height) - 1def 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: # 如果节点不存在返回returnif 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 = Noneprint("-----------------")for i in reversed(ancestor_dic.get(node_1)): # 从下往上查血缘关系print(i.value)if i in ancestor_dic.get(node_2): # 如果查找到的血缘关系在node_2的血缘关系表中flag = i # 获取共同祖先节点breakprint("-----------------")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_22: 共同祖先(如果不是根节点 会直接向上传递 直到最后返回)3: 左节点为 None 返回右节点 否则返回左节点'''# 当前节点为空, 当前节点为节点1 或者当前节点为节点2if not cur_node or cur_node == node_1 or cur_node == node_2:return cur_node # 1left_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 # 2return left_node if left_node else right_node # 3@staticmethoddef find_successor_node(node):"""找 node 的后继节点在二叉树的中序遍历中, node的下一个节点叫 node 的后继节点:param node: 带有父节点指针的节点 且假定整棵树节点都有父节点的指针:return: node的后继节点, 如果没有 返回 None"""if not node:returnif rn := node.right:# 如果该节点有右子树, 则右子树的最左节点为 node 的后继节点while rn.left: rn = rn.leftreturn rnelse:parent = node.parentwhile parent and parent.right is node:'''如果父节点不为 None 且该节点为父节点的右孩子向上查找一直查找到某一层为该层父节点的左孩子则此时的父节点即为 node 的后继节点如果最后找到父节点为 None 即找到了根节点 则说明该节点为此树的最后一个节点 无后继节点 返回 None'''node = parentparent = node.parentreturn parentdef Serialization(self):"""序列化:return:"""if not self.tree:returnq = 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.treedef origami_problem(self, n):"""折纸问题:param n: 折了几次:return:"""if n < 1:returnroot_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_nodereturn self.mid()
四、自定义打印函数
def print_nodes_value(nodes_arr):'''根据遍历序列输出:param nodes_arr::return:'''if not nodes_arr:returnfor 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 其余均为博主自己编写(可能有些跟视频中的算法相重合)
这篇关于[算法入土之路]二叉树的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!