树的遍历及其基本操作

188 阅读3分钟

主要内容:

  1. 二叉树的遍历(先序、中序、后序、广度优先遍历)的递归和迭代实现
  2. 二叉树的深度,二叉树到叶子节点的所有路径

首先,定义二叉树类

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

img

二叉树的遍历

被围绕的区域

先序遍历

遍历顺序:根节点——左孩子——右孩子(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);
    }
}

左叶子之和

leetcode-cn.com/problems/su…

递归:树的左叶子之和=左子树的左叶子之和+右子树的左叶子之和+当前叶子节点的值(如果当前节点是父节点的左孩子)

重点在于判断当前节点是不是其父节点的左孩子,因此在递归函数中传入参数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

二叉树题目的思路大多是递归,就是划分子问题,然后递归地构建左子树和右子树。

后序遍历最后一个节点为整棵树的根节点,因此可以确定根节点。

再根据中序序列得到左子树的中序序列和右子树的中序序列。

不管中序还是后序,左右子树的节点个数是相同的,因此可以得到左子树和右子树的后序序列。

递归地构建左右子树。

代码见从中序与后序遍历序列构造二叉树从前序与中序遍历序列构造二叉树