[算法入土之路]二叉树

2024-03-24 23:08
文章标签 算法 二叉树 入土

本文主要是介绍[算法入土之路]二叉树,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、二叉树知识点概述

二叉树节点结构

        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 其余均为博主自己编写(可能有些跟视频中的算法相重合)

这篇关于[算法入土之路]二叉树的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/843076

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

dp算法练习题【8】

不同二叉搜索树 96. 不同的二叉搜索树 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n = 3输出:5 示例 2: 输入:n = 1输出:1 class Solution {public int numTrees(int n) {int[] dp = new int

Codeforces Round #240 (Div. 2) E分治算法探究1

Codeforces Round #240 (Div. 2) E  http://codeforces.com/contest/415/problem/E 2^n个数,每次操作将其分成2^q份,对于每一份内部的数进行翻转(逆序),每次操作完后输出操作后新序列的逆序对数。 图一:  划分子问题。 图二: 分而治之,=>  合并 。 图三: 回溯:

最大公因数:欧几里得算法

简述         求两个数字 m和n 的最大公因数,假设r是m%n的余数,只要n不等于0,就一直执行 m=n,n=r 举例 以18和12为例 m n r18 % 12 = 612 % 6 = 06 0所以最大公因数为:6 代码实现 #include<iostream>using namespace std;/