二叉树的遍历思想在很多算法中体现。快速排序的本质是二叉树的先根遍历,归并排序和分治算法本质是后根遍历。
"二叉树题目的难点在于如何通过题目的要求思考出 每一个节点需要做什么,在什么时候做 "。

文章目录

  • 1.翻转二叉树
  • 2.填充二叉树节点的右侧指针*
  • 3.将二叉树按先根顺序展开成链表(即只有右子节点的树)
  • 4.根据数组构建最大二叉树
  • 5.通过前序和中序遍历结果构造二叉树
  • 6.寻找相同子树——序列化*
  • 7.BST中第k大的元素
  • 8.BST转换为累加树
  • 9.判断BST是否合法
  • 10.删除BST指定节点**
  • 11.路径总和III
  • 12.合并二叉树
  • 13.BST转双向有序循环链表*
  • 14.树的子结构

1.翻转二叉树

写递归函数时首先确定递归出口。
本题可以使用先根遍历,也可以使用后根遍历。
每个节点应该让自己的左右子树翻转后,交换其位置。也可以先交换再翻转。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):def invertTree(self, root):""":type root: TreeNode:rtype: TreeNode"""# 递归出口if root is None:return None# 后根left_node = self.invertTree(root.left)right_node = self.invertTree(root.right)root.right = left_noderoot.left = right_nodereturn root

2.填充二叉树节点的右侧指针*

leetcode116题。拉不拉东的经典递归法:
每个节点应该把左子节点指向右子节点,并且当该节点next不为空时,其右子节点指向next的左子节点。

"""
# Definition for a Node.
class Node(object):def __init__(self, val=0, left=None, right=None, next=None):self.val = valself.left = leftself.right = rightself.next = next
"""class Solution(object):def connect(self, root):""":type root: Node:rtype: Node"""if root is None:return Noneself.connectTowNodes(root.left, root.right)return rootdef connectTowNodes(self, node1, node2):# 递归出口if node1 is None:returnnode1.next = node2self.connectTowNodes(node1.left, node1.right)self.connectTowNodes(node2.left, node2.right)self.connectTowNodes(node1.right, node2.left)

另一种巧妙的思路,避免了新建方法。利用先根遍历的特性,从上往下进行连接。当前节点的next不为None时,必有右兄弟,需要将右兄弟的左子节点和当前节点的右子节点连接。

"""
# Definition for a Node.
class Node(object):def __init__(self, val=0, left=None, right=None, next=None):self.val = valself.left = leftself.right = rightself.next = next
"""class Solution(object):def connect(self, root):""":type root: Node:rtype: Node"""if root is None or root.left is None:return root# 连接相同父亲的节点root.left.next = root.rightif root.next is not None:root.right.next = root.next.leftself.connect(root.left)self.connect(root.right)return root

3.将二叉树按先根顺序展开成链表(即只有右子节点的树)

题目要求:

每个节点应该把自己的左右子树拉直,并把拉直的左子树放到原来右子树的位置,把拉直的右子树接在拉直的左子树下面。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):def flatten(self, root):""":type root: TreeNode:rtype: None Do not return anything, modify root in-place instead."""# 递归出口if root is None:return None# 先拉平左右子树left_flatten = self.flatten(root.left)right_flatten = self.flatten(root.right)# 后根遍历,处理根节点的位置# 按照先根遍历顺序,将左子树的结果,拼接在根节点的右边,并将右子树的结果追加在后面root.right = left_flattenroot.left = Noneif (left_flatten is None):root.right = right_flattenelse:while (left_flatten.right is not None):left_flatten = left_flatten.rightleft_flatten.right = right_flattenreturn root

4.根据数组构建最大二叉树

  • 最大二叉树:左子树是由数组中,最大元素左边的数字构成的最大二叉树。
  • 先根遍历,思想类似于快速排序。

还是要明白每个节点该干什么,什么时候干。
每个节点应该找到数组的最大元素,将左右数组分别送给左右子树。

class Solution(object):def constructMaximumBinaryTree(self, nums):""":type nums: List[int]:rtype: TreeNode"""if len(nums) == 0:return None# 寻找最大值下标max_idx = nums.index(max(nums))return TreeNode(nums[max_idx], self.constructMaximumBinaryTree(nums[: max_idx]), self.constructMaximumBinaryTree(nums[max_idx + 1: ]))

5.通过前序和中序遍历结果构造二叉树

  • 前序的第一个元素为整棵树的根节点,中序遍历中,根节点左侧元素构成左子树,右侧元素构成右子树,递归构造即可。
    知道这个规律后很简单,和4几乎完全一致。
# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):def buildTree(self, preorder, inorder):""":type preorder: List[int]:type inorder: List[int]:rtype: TreeNode"""# 递归出口length = len(preorder)if length == 0:return None# 确定左右子树的前序、中序序列root_val = preorder[0]root_inoreder_index = inorder.index(root_val)left_node_num = root_inoreder_index  # 左子树的节点数# 构造左右子树left_tree = self.buildTree(preorder[1:left_node_num+1], inorder[:root_inoreder_index])right_tree = self.buildTree(preorder[1+left_node_num:], inorder[root_inoreder_index+1:])# 指向左右子树return TreeNode(val=root_val, left=left_tree, right=right_tree)

6.寻找相同子树——序列化*

  • 大致思路是,采用后根遍历,自底向上进行(因为要先知道底层的结构,才能判断有无相同子树)。
  • 既然比较“相同”,需要设置备忘录,记录已经见过的树的结构。
  • 最后通过的做法,引入序列化,字符串比较替换递归的节点比较。
class Solution(object):def findDuplicateSubtrees(self, root):""":type root: TreeNode:rtype: List[TreeNode]"""# 寻找重复子树,需要自底向上进行,采用后根遍历# 每个节点需要将自己的左右子树记录待查,如果重复则添加到输出结果res = []  # 最终结果,存放(树,次数,节点)self.find(root, res)subtrees = []for i in range(len(res)):if res[i][1] > 1:subtrees.append(res[i][2])return subtreesdef find(self, node, result):# 递归出口if node is None:returnself.find(node.left, result)self.find(node.right, result)# 后根# 判断是否见过该子树node_sequence = self.transferToSequence(node)for i in range(len(result)):if result[i][0] == node_sequence:old_time = result[i][1]result[i] = (node_sequence, old_time+1, node)return# 没见过,将其加入到字典中result.append((node_sequence, 1, node))# 序列化二叉树def transferToSequence(self, node):if node is None:return "#"return str(node.val) + "," + self.transferToSequence(node.left) + "," + self.transferToSequence(node.right)

7.BST中第k大的元素

凡提及BST,一定用到的思路是:
BST左子树的节点都小于当前节点,右子树节点都大于当前节点。
BST的中序遍历是升序序列。

class Solution(object):cnt = 0def kthLargest(self, root, k):""":type root: TreeNode:type k: int:rtype: int"""self.cnt = 0def findK(node, k):if node is None:return Noner = findK(node.right, k)if r is not None:  # 右子树某节点为第k大return rself.cnt += 1if self.cnt == k:  # 当前节点数为第k大return node.vall = findK(node.left, k)  # 左子树某节点为第k大            return lreturn findK(root, k)

8.BST转换为累加树

  • 题目要求:给出BST的根节点,该树的节点值各不相同,请你将其转换为累加树,使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
  • 从题目得知,累加操作要从最大的元素开始,直到最小的元素。
  • 很快想到要用 右子树->根节点->左子树 的中根遍历顺序解决问题。
class Solution(object):temp = 0def convertBST(self, node):""":type root: TreeNode:rtype: TreeNode"""# 对于根节点,首先处理右子树,改变根节点值,再处理左子树# 左子树需要根节点的信息!所以需要先右再左if node is None:return Noneself.convertBST(node.right)self.temp += node.valnode.val = self.tempself.convertBST(node.left)return node

9.判断BST是否合法

最简单的思路是中序遍历检查是否递增,只需记录前一个节点的值
需要自底向上进行,后根遍历。
对于每个节点,保证自己的左右子树为BST,同时节点大于左子树最大值,小于右子树最小值。

class Solution(object):def isValidBST(self, root):""":type root: TreeNode:rtype: bool"""# 保证每个节点大于左子树最大值,小于右子树最小值def findLeftMax(node):if node is None or node.left is None:return Nonenode = node.leftwhile (node.right is not None):node = node.rightreturn node.valdef findRightMin(node):if node is None or node.right is None:return Nonenode = node.rightwhile (node.left is not None):node = node.leftreturn node.valif root is None:return Trueelse:l = findLeftMax(root)r = findRightMin(root)l = (root.val - 1) if l is None else lr = (root.val + 1) if r is None else r return l < root.val and r > root.val and self.isValidBST(root.left) and self.isValidBST(root.right)

10.删除BST指定节点**

对于不具有子节点的待删除点,直接删除即可。
具有单个子节点,用子节点替换。
具有两个子节点,需要用左子树的最大节点/右子树的最小节点替换。

利用节点作为返回值,减少冗余代码。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):def deleteNode(self, root, key):""":type root: TreeNode:type key: int:rtype: TreeNode"""# 不存在待删除节点if root is None:return None# 先根遍历# 分情况讨论:待删节点无子节点、有一个子节点、有两个子节点elif root.val == key:if root.left is None and root.right is None:return Noneelif root.left is not None and root.right is None:return root.leftelif root.left is None and root.right is not None:return root.rightelse:# 有两个子节点时,让左侧最大或右侧最小的节点替代该节点# 这里选择用左侧最大的节点left_node = root.leftleft_node_father = rootwhile (left_node.right is not None):left_node_father = left_nodeleft_node = left_node.rightroot.val = left_node.valroot.left = self.deleteNode(root.left, root.val)  # ***精髓***# 当前节点不是待删节点,交给子树处理elif root.val > key:root.left = self.deleteNode(root.left, key)else:root.right = self.deleteNode(root.right, key)return root

11.路径总和III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

  • 使用DFS+前缀和的思路。DFS的一个明显好处是,每条正在搜索的路径一定符合题目中对路径的要求
  • 将 path[0]设置为0,可以方便统计从根节点到某个节点的前缀和
class Solution(object):res = 0def pathSum(self, root, targetSum):""":type root: TreeNode:type targetSum: int:rtype: int"""self.res = 0path = [0]  # 记录当前路径的前缀和def dfs(node, targetSum):# 出口if node is None:return False# 一般情况path.append(node.val + path[-1])for i in range(len(path) - 1):if path[-1] - path[i] == targetSum:self.res += 1# 执行选择并撤销if dfs(node.left, targetSum):path.pop()if dfs(node.right, targetSum):path.pop()return Truedfs(root, targetSum)return self.res--------------------------------------这样也是可以的-----------------------------------class Solution(object):res = 0def pathSum(self, root, targetSum):""":type root: TreeNode:type targetSum: int:rtype: int"""self.res = 0path = [0]  # 记录当前路径的前缀和def dfs(node, targetSum):# 出口if node is None:return# 一般情况path.append(node.val + path[-1])for i in range(len(path) - 1):if path[-1] - path[i] == targetSum:self.res += 1dfs(node.left, targetSum)dfs(node.right, targetSum)path.pop()dfs(root, targetSum)return self.res

12.合并二叉树

合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

class Solution(object):def mergeTrees(self, root1, root2):""":type root1: TreeNode:type root2: TreeNode:rtype: TreeNode"""if root1 is None and root2 is None:return Noneelif root1 is not None and root2 is None:return root1elif root1 is None and root2 is not None:return root2else:return TreeNode(root1.val + root2.val, self.mergeTrees(root1.left, root2.left), self.mergeTrees(root1.right, root2.right))

13.BST转双向有序循环链表*

  • 利用中序遍历BST得到递增序列的规则,使用全局的遍历 head 记录链表头,pre 记录之前的节点
  • 一个比较巧妙的思想:递归结束时 pre 是最后的节点,和 head 首尾相连
class Solution(object):head = Nonepre = Nonedef treeToDoublyList(self, root):""":type root: Node:rtype: Node"""if root is None:return Noneself.head = Noneself.pre = None# 中序遍历树,并执行连接def midorder(node):# 递归出口if node is None:return None# 中序遍历midorder(node.left)if self.pre is None:  # 记录首节点self.head = nodeelse:self.pre.right = nodenode.left = self.preself.pre = nodemidorder(node.right)midorder(root)self.head.left = self.preself.pre.right = self.headreturn self.head

14.树的子结构

  • 判断判断B是不是A的子结构
  • 最后一行是核心:分别判断 pRoot2 是否是以 pRoot1、pRoot1.left、pRoot1.right 为根的树的子结构
    HasSubTree(a, b):a中是否包含b,a不一定是首节点
    find(a, b):从节点a和b开始,a是否包含b
class Solution:def HasSubtree(self, pRoot1, pRoot2):def find(n1, n2):if not n2:return True elif not n1:return Falseelif n1.val != n2.val:return Falseelse:return find(n1.left, n2.left) and find(n1.right, n2.right)if not pRoot1 or not pRoot2:return Falsereturn find(pRoot1, pRoot2) or self.HasSubtree(pRoot1.left, pRoot2) or self.HasSubtree(pRoot1.right, pRoot2)

数据结构 - 二叉树相关推荐

  1. 数据结构 -- 二叉树

          这篇文章介绍的是经典的数据结构--二叉树,在这篇文章里介绍了几乎二叉树的所有操作.       二叉树给我们最重要的印象莫过于递归,因为这棵树就是递归的,所以,我在解决各个问题时大部分都用 ...

  2. 数据结构 - 二叉树 - 面试中常见的二叉树算法题

    数据结构 - 二叉树 - 面试中常见的二叉树算法题 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组.链表).栈与队列.树(二叉树.二叉查找树.平衡二叉树.红黑树).图 ...

  3. 数据结构——二叉树的递归算法

    二叉树的结构定义: typedef struct BiNode {TElemType data;struct BiNode *lchild;struct BiNode *rchild; }BiNode ...

  4. 数据结构——二叉树的层次遍历进阶

    之前的一个博客 数据结构--二叉树的层次遍历看完这个,可以简单实现下面的问题 问题: 1.计算二叉树的最大宽度(二叉树的最大宽度是指二叉树所有层中结点个数的最大值. 2.用按层次顺序遍历二叉树的方法, ...

  5. 数据结构----二叉树叶子结点到根节点的高度计算

    数据结构----二叉树叶子结点到根节点的高度计算 代码: #include<stdio.h> #include<stdlib.h> typedef struct bstTree ...

  6. 数据结构 二叉树的存储结构_线程二叉树| 数据结构

    数据结构 二叉树的存储结构 线程二叉树 (Threaded Binary Tree ) A binary tree can be represented by using array represen ...

  7. 二叉树----数据结构:二叉树的三种遍历及习题

    二叉树----数据结构:二叉树的三种遍历,利用递归算法. 关于二叉树的遍历,应用非常广泛,不单单是访问打印结点,还可以进行一系列的操作,如赋值.删除.查找.求二叉树的深度等等. 有递归和非递归两种算法 ...

  8. 数据结构-二叉树入门Go语言实现

    数据结构-二叉树入门Go语言实现 之前我们一直在谈的是一对一的线性结构,可现实中,还有很多一对多的情况需要处理,所以我们需要研究这种一对多的数据结构--"树",考虑它的各种特性,来 ...

  9. 数据结构——二叉树——特点及性质

    数据结构--二叉树--特点及性质 二叉树(Binary Tree)是n(n=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的.分别称为根结点的左子树和右子树的二 ...

  10. 数据结构——二叉树总结

    数据结构-二叉树总结 写在前面 二叉树遍历 递归实现先.中.后序遍历 非递归遍历 先序非递归 中序非递归 后序非递归 层次遍历 二叉树还原 先序中序建树 后序中序建树 层次中序建树 二叉树应用 二叉查 ...

最新文章

  1. TreeView控件结合js树形选择 .
  2. Python 之数据类型
  3. java web 加载类_Tomcat类加载机制
  4. verilog学习记(verilog翻译成c)
  5. 【带着canvas去流浪】 (3)绘制饼图
  6. yii 操作数据库的三种方法
  7. 我在使用Spring Gateway时遇到的一些坑
  8. INTEL CPU 内核漏洞问题该如何处理?
  9. FullCalendar日历控件vue使用记录
  10. 计算机具有理性和逻辑思维吗,我们的理性和逻辑思维能力真的有那么重要吗?...
  11. Python笔记(三):集合、文件、字符编码
  12. 常用的Eclipse 快捷键
  13. 图解:什么是 5G?5G 为什么那么屌?
  14. Python的人工智能模拟框架
  15. Git如何上传代码到远程仓库(GiteeGithub)
  16. API接口名称(包括在请求地址中)[item_search,item_get,item_search_shop等] item_get - 根据ID取商品详情
  17. 2017京东校招在线编程题——集合
  18. 什么是自动化运维?自动化运维必备技能有哪些?
  19. 高防服务器稳定性原因,企业租用高防服务器有什么原因呢?
  20. 大数据基础(专 2022春)

热门文章

  1. rest_framework11:jwt简单例子/自定制基于jwt认证类
  2. sql将html转成excel,使用SQL*PLUS,构建完美excel或html输出
  3. [转]WebView长按弹出复制粘贴
  4. SQL一键备份用户数据库
  5. ENVI5.4完美实现MODIS NDVI数据格式转换和投影变换
  6. SQL Server 2005将某些数据库行为设置为与指定的 SQL Server 版本兼容
  7. Android之TextUtils类介绍
  8. 无代码iVX编程实现简单 小蜜蜂 经典游戏
  9. 2560x1600分辨率高吗_做设计还弄不清分辨率和像素之间的关系,来了解下他们是怎么换算...
  10. 100以内素数之和python123_python质数,水仙花数,简单猜拳游戏等