主要内容:
- 二叉树的遍历(先序、中序、后序、广度优先遍历)的递归和迭代实现
- 二叉树的深度,二叉树到叶子节点的所有路径
首先,定义二叉树类
class TreeNode:
def __init__(self,x):
self.val=x
self.left=None
self.right=None
class TreeNode {//树
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
二叉树的遍历分为深度优先遍历(dfs)和广度优先遍历(bfs)。其中深度优先遍历又分为先序遍历、中序遍历、后序遍历。bfs又称为层次遍历。
- dfs的迭代实现用stack
- bfs的迭代实现用queue
二叉树的遍历
先序遍历
遍历顺序:根节点——左孩子——右孩子(A-B-D-E-C-F)
递归实现:
def preorder(root):
if not root:
return
print(root.val)
preorder(root.left)
preorder(root.right)
public void preorder(TreeNode root) {
if (root==null)
return;
System.out.println(root.val);
this.preorder(root.left);
this.preorder(root.right);
}
迭代实现:
def preorder(root):
stack=[root]
while stack:
s=stack.pop(-1)
if s:
print(s.val)
stack.append(s.right)
stack.append(s.left)
public void preorder(TreeNode root) {
Deque<TreeNode> st=new LinkedList<> ();
st.addFirst(root);
while (!st.isEmpty()) {
TreeNode s=st.pollFirst();
if (s!=null) {
System.out.println(s.val);
st.addFirst(s.right);
st.addFirst(s.left);
}
}
}
中序遍历
遍历顺序:左孩子——根节点——右孩子(D-B-E-A-C-F)
递归实现:
def inorder(root):
if (not root):
return
inorder(root.left)
print(root.val)
inorder(root.right)
public void inorder(TreeNode root) {
if (root==null) {
return;
}
this.inorder(root.left);
System.out.println(root.val);
this.inorder(root.right);
}
迭代实现:
def inorder(root):
stack=[]
while stack or root:
while root:#下行循环,直到找到第一个叶子节点
stack.append(root)
root=root.left
root=stack.pop(-1)
print(root.val)
root=root.right
public void inorder(TreeNode root) {
Deque<TreeNode> st=new LinkedList<> ();
while (!st.isEmpty() || root!=null) {
while (root!=null) {
st.addFirst(root);
root=root.left;
}
root=st.pollFirst();
System.out.println(root.val);
root=root.right;
}
}
后序遍历
遍历顺序:左孩子——右孩子——根节点(D-E-B-F-C-A)
递归实现:
def postorder(root):
if (not root):
return
postorder(root.left)
postorder(root.right)
print(root.val)
public void postorder(TreeNode root) {
if (root==null)
return;
this.postorder(root.left);
this.postorder(root.right);
System.out.println(root.val);
}
迭代实现:
def postorder(root):
stack=[]
while stack or root:
while root:#下行循环,直到找到第一个叶子节点
stack.append(root)
if root.left:#左孩子压栈、没左孩子就右孩子压栈
root=root.left
else:
root=root.right
s=stack.pop(-1)
print(s.val)
#如果当前节点是栈顶节点的左孩子,则遍历右孩子
if stack and s==stack[-1].left:
root=stack[-1].right
else:
root=None
层次遍历
遍历顺序:一层层地遍历(A-B-C-D-E-F)
迭代实现:
def bfs(root):
queue=[root]
while queue:
n=len(queue)
for i in range(n):
q=queue.pop(0)
if q:
print(q.val)
queue.append(q.left if q.left else None)
queue.append(q.right if q.right else None)
public void bfs(TreeNode root) {
if (root==null)
return;
Queue<TreeNode> q=new LinkedList<> ();
q.offer(root);
while (!q.isEmpty()) {
int n=q.size();
while (n>0) {
TreeNode node=q.poll();
System.out.println(node.val);
if (node.left!=null) {
q.offer(node.left);
}
if (node.right!=null) {
q.offer(node.right);
}
n--;
}
}
}
基本操作
求二叉树的深度、直径、最长同值路径其实都是后序遍历的过程
二叉树的最大深度
def maxDepth(root):
if not root:
return 0
return 1+max(maxDepth(root.left),maxDepth(root.right))
public int maxDepth(TreeNode root) {
if (root==null)
return 0;
return Math.max(this.maxDepth(root.left), this.maxDepth(root.right))+1;
}
树的直径
class Solution {
int dia=0;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return dia;
}
private int depth(TreeNode root) {//求树的高度的过程中计算每个子树的直径
if (root==null)
return 0;
int l=depth(root.left);
int r=depth(root.right);
dia=Math.max(dia, l+r);
return Math.max(l, r)+1;
}
}
最长同值路径
class Solution {
int res;
public int longestUnivaluePath(TreeNode root) {
maxLen(root);
return res;
}
private int maxLen(TreeNode root) {//求以root为起点的最长同值路径
if (root==null) {
return 0;
}
int l=maxLen(root.left);
int r=maxLen(root.right);
if (root.left!=null) {
if (root.left.val==root.val) {
l+=1;
}else {
l=0;
}
}
if (root.right!=null) {
if (root.right.val==root.val) {
r+=1;
}else {
r=0;
}
}
res=Math.max(res, l+r);
return Math.max(l, r);
}
}
二叉树中的最大路径和
class Solution {
int res;
public int maxPathSum(TreeNode root) {
res=root.val;//注意res初值,路径要求至少包含一个节点
maxSum(root);
return res;
}
private int maxSum(TreeNode root) {
if (root==null) {
return 0;
}
int l=maxSum(root.left);
if (l<0){
l=0;
}
int r=maxSum(root.right);
if (r<0){
r=0;
}
res=Math.max(res, l+r+root.val);
return Math.max(l+root.val, r+root.val);
}
}
左叶子之和
递归:树的左叶子之和=左子树的左叶子之和+右子树的左叶子之和+当前叶子节点的值(如果当前节点是父节点的左孩子)
重点在于判断当前节点是不是其父节点的左孩子,因此在递归函数中传入参数dir来表示左孩子/右孩子
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
return leftSum(root, 1);
}
private int leftSum(TreeNode root,int dir) {//0代表是左孩子,1是右孩子
int sum=0;
if (root==null) {
return 0;
}
if (root.left==null && root.right==null && dir==0) {
return root.val;
}
sum+=leftSum(root.left, 0);
sum+=leftSum(root.right, 1);
return sum;
}
}
把二叉搜索树转换为累加树
BST中序遍历是升序排列,反过来就是降序,然后累加到当前节点上即可
class Solution {
int num=0;
public TreeNode convertBST(TreeNode root) {
if (root==null)
return null;
convertBST(root.right);
root.val+=num;
num=root.val;
convertBST(root.left);
return root;
}
}
二叉树的最近公共祖先
后序遍历
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root==p or root==q:
return root
left=self.lowestCommonAncestor(root.left,p,q)
right=self.lowestCommonAncestor(root.right,p,q)
if left and right:# p,q分别在root的异侧
return root
if not left and not right:# root的左/右子树中都不包含p,q
return None
if left: # p,q都不在root的右子树中
return left
if right:# p,q都不在root的左子树中
return right
构建二叉树
根据前序和中序可以构造一棵二叉树,根据后序和中序也可以构造一棵二叉树,反正必须要有中序才能构建。因为没有中序,无法确定树的形状。例如先序为[1,2],后序为[2,1]的树可以有:
1
/
2
1
\
2
二叉树题目的思路大多是递归,就是划分子问题,然后递归地构建左子树和右子树。
后序遍历最后一个节点为整棵树的根节点,因此可以确定根节点。
再根据中序序列得到左子树的中序序列和右子树的中序序列。
不管中序还是后序,左右子树的节点个数是相同的,因此可以得到左子树和右子树的后序序列。
递归地构建左右子树。