算法与数据体系课笔记之- 10.二叉树(上)结构与遍历

10.二叉树(上)结构与遍历 总览

在这里插入图片描述

笔记思维导图链接

算法与数据结构思维导图

参考左程云体系算法课程笔记
参考慕课网算法体系课程笔记

常见题目汇总:

1:二叉树深度遍历

1.1 二叉树深度遍历递归实现

其实,每个节点在递归调用时,都被访问了三次,三种遍历,区别只是再哪一次访问时进行处理

在这里插入图片描述
  • 前序遍历
public List<Integer> preorderTraversal(TreeNode root) {
		 List<Integer> res = new ArrayList<>();
		 preDFS(root, res);
	     return res;
    }

	private void preDFS(TreeNode node, List<Integer> list) {
		if(node == null) return;
		
		list.add(node.val); // 第一次访问时就处理节点
		preDFS(node.left, list);
		preDFS(node.right, list);
	}
  • 中序遍历
    // 中序遍历
	private void inDFS(TreeNode node, List<Integer> list) {
		if (node == null) return;

		inDFS(node.left, list);
		list.add(node.val); // 第二次访问该节点时处理
		inDFS(node.right, list);
	}
  • 后续遍历
   // 后序遍历
	private void posDFS(TreeNode node, List<Integer> list) {
		if (node == null) return;

		posDFS(node.left, list);	
		posDFS(node.right, list);
        list.add(node.val); // 第三次访问该节点时处理
	}
在这里插入图片描述

1.2 二叉树深度遍历非递归实现

前序遍历
题目链接:

144. 二叉树的前序遍历

题解:
  • 1.由于是深度遍历,使用辅助空间栈来对应节点的访问和处理过程
    • 即,访问即压入栈,处理即将此节点弹出栈进行处理
  • 2.对于头,访问和处理都是先从头节点开始,故访问到根节点时,先压入栈,并随即处理栈顶元素
  • 3.处理完栈顶元素后,开始访问并处理子节点,
    • 由于栈的弹出顺序与入栈顺序相反,故,访问顺序为左右,压栈顺序为右左
    • 左右均压入栈后,每次处理栈顶元素,即每次处理刚开始访问的头节点(相对于父节点的左节点)
    • 第一轮左节点访问处理完后,再处理深度最深的右节点,即此时的栈顶元素,作为头节点循环进行
private  List<Integer> list;
	private  List<Integer> list;
	private Stack<TreeNode> stack;
	private TreeNode cur;
	// 前序遍历
	public  List<Integer> preorderTraversal(TreeNode root) {
		list = new ArrayList<>();
		if(root == null) return list; // 注意不输入节点时,返回[],不是null
//	- 1.由于是深度遍历,使用辅助空间栈来对应节点的访问和处理过程
//	  - 即,访问即压入栈,处理即将此节点弹出栈进行处理
		stack = new Stack<>();
		
//	- 2.对于头,访问和处理都是先从头节点开始,故访问到根节点时,先压入栈,并随即处理栈顶元素
		stack.push(root); 
		while(!stack.isEmpty()) {
			cur = stack.pop(); // 保存要处理的节点
			list.add(cur.val);		// 处理刚访问的节点
//	- 3.处理完栈顶元素后,开始访问并处理子节点,
//	  - 由于栈的弹出顺序与入栈顺序相反,故,访问顺序为左右,压栈顺序为右左
			if(cur.right != null) stack.push(node.right);
			if(cur.left != null) stack.push(node.left);
		}
		return list;
	}
复杂度分析:
  • 时间复杂度:O(n)
    • 其中 n 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次
  • 空间复杂度:O(n)
    • 空间复杂度取决于栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别。
后序遍历
题目链接:

145. 二叉树的后序遍历

题解:
  • 后续遍历顺序为左右头,将前序遍历该成头右左,再反转,即是左右头的顺序
  • 可以直接使用库函数集合容器的reverse方法
  • 也可以再使用一个栈来自己实现逆序操作
// 使用Collections.reverse(list)逆序
class Solution {	
    private  List<Integer> list;
	private Stack<TreeNode> stack;
	private TreeNode cur;

    public List<Integer> postorderTraversal(TreeNode root) {
       list = new ArrayList<>();
		if(root == null) return list; // 注意不输入节点时,返回[],不是null
//	- 1.由于是深度遍历,使用辅助空间栈来对应节点的访问和处理过程
//	  - 即,访问即压入栈,处理即将此节点弹出栈进行处理
		stack = new Stack<>();
		
//	- 2.对于头,访问和处理都是先从头节点开始,故访问到根节点时,先压入栈,并随即处理栈顶元素
		stack.push(root); 
		while(!stack.isEmpty()) {
			cur = stack.pop(); // 保存要处理的节点
			list.add(cur.val);		// 处理刚访问的节点
//	- 3.处理完栈顶元素后,开始访问并处理子节点,
//	  - 由于栈的弹出顺序与入栈顺序相反,故,访问顺序为右左,压栈顺序为左右
			if(cur.left != null) stack.push(cur.left);
			if(cur.right != null) stack.push(cur.right);
		}
		Collections.reverse(list);
		return list;
    }
}
// 使用两个栈实现逆序
class Solution {
	private  List<Integer> list;
	private Stack<TreeNode> stack;
    private Stack<TreeNode> stack2;
	private TreeNode cur;

    public List<Integer> postorderTraversal(TreeNode root) {
       list = new ArrayList<>();
		if(root == null) return list; // 注意不输入节点时,返回[],不是null
//	- 1.由于是深度遍历,使用辅助空间栈来对应节点的访问和处理过程
//	  - 即,访问即压入栈,处理即将此节点弹出栈进行处理
		stack = new Stack<>();
		stack2= new Stack<>();
//	- 2.对于头,访问和处理都是先从头节点开始,故访问到根节点时,先压入栈,并随即处理栈顶元素
		stack.push(root); 
		while(!stack.isEmpty()) {
			cur = stack.pop(); // 保存要处理的节点
			stack2.push(cur);		// 处理刚访问的节点
//	- 3.处理完栈顶元素后,开始访问并处理子节点,
//	  - 由于栈的弹出顺序与入栈顺序相反,故,访问顺序为右左,压栈顺序为左右
			if(cur.left != null) stack.push(cur.left);
			if(cur.right != null) stack.push(cur.right);
		}
		while(!stack2.isEmpty()) {
			list.add(stack2.pop().val);
		}
		return list;
    }
}
中序遍历
题目链接:

94. 二叉树的中序遍历

题解:
  • 1.由于访问的的顺序和处理的顺序不一致,需要使用指针指向要处理的节点
  • 2.先访问:用指针,指向左分支中每个要访问的节点,先不处理,而是放入栈中,一直深到最左位置
    • 初始stack为null,先不能加cur,与后面判断逻辑冲突
    • 故要让循环能开始,需要再给定一个开始条件cur!=null;
  • 3.再处理;指针指向要处理的节点,即栈顶元素
    • 因为左边都访问完了,回到头节点的第二次访问时机进行处理
  • 4.指针指向处理节点的右节点,以此节点为根节点,继续下一轮的访问处理,
    • 维持左中右遍历顺序
// 中序遍历
	public List<Integer> inorderTraversal(TreeNode root) {
		list = new ArrayList<>();
		if(root == null) return list;
        
//		- 1.由于访问的的顺序和处理的顺序不一致,需要使用指针指向要处理的节点
		stack = new Stack<>();
		cur = root;    // cur先指向根节点,深入,直到指向要处理的节点
		
//		- 2.先访问:用指针,指向左分支中每个要访问的节点,先不处理,而是放入栈中,一直深到最左位置
		while(cur != null || !stack.isEmpty()) { // cur != null让循环开始
			while(cur != null) {
				stack.push(cur);
				cur = cur.left;
			}		
//		- 3.再处理;指针指向要处理的节点,因为左边都访问完了,回到头节点的第二次访问
			cur = stack.pop();
			list.add(cur.val);
//		- 4.指针指向处理节点的右节点,以此节点为根节点,继续下一轮的访问处理,维持左中右遍历顺序
			cur = cur.right;
		}
		return list;
	}

2. 二叉树公共祖先问题

2.1 证明题:二叉树某一节点x祖先节点的交集

题意:
  • 证明:已知X是二叉树中某一个节点, 整棵树的先序遍历结果, 整棵树后序遍历结果, 则有结论:

    • X先序遍历之前的节点定义为集合A, X后序遍历之后的节点定义为集合B,
    • 则 A∩B得到的结果有且仅是X的祖先节点
  • 例如:二叉树结构如下:A,B集合的交集为a,c,均为X的祖先节点

    img
题解:
  • 证明的思路是:

    • 1.先证明X的祖先节点一定出现在交集中
    • 2.非X祖先的节点一定不出现在交集中
  • 1.证明X的祖先节点一定出现在交集中

    在这里插入图片描述
    • 先序遍历顺序:头左右

      • 故,作为X的祖先节点,一定是X的头节点,顺序一定出现在X之前,
      • 即祖先节点一定包含在A中
    • 后序遍历顺序:左右头

      • 故,作为X的祖先节点,一定是X的头节点,顺序一定出现在X之后,
      • 即祖先节点一定包含在B中
    • 综上:X的祖先节点一定包含在A,B的交集中

  • 2.非X祖先的节点一定不出现在交集中

    • X的孩子

      • 先序遍历中,X孩子节点一定在X后面,故,A中一定没有,可排除

        在这里插入图片描述
    • X作为左树姿态的右兄弟节点

      • 在前序遍历中,X的右兄节点一定在X后面,故集合A中也一定没有,可排除

        在这里插入图片描述
    • X作为右树姿态的左兄弟节点

      • 在后序遍历中,X的左兄节点一定在X前面,故集合中一定没有,可排除

2.2 二叉搜索树的最近公共祖先

题目链接:

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

题意:
  • 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先
  • 最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
  • 题中说明:
    • 所有节点的值都是唯一的。
    • p、q 为不同节点且均存在于给定的二叉搜索树中

示例:

img

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。
题解:

二叉搜索树,根节点与子节点有大小关系,因此只需循环遍历节点,可以凭借此进行查找

  • 1.从根节点开始遍历判断是不是满足的节点
  • 2.主要解决走哪个分支,
    • cur若不是最近公共祖先,p,q节点一定在cur的一边,继续深入找一边即可
    • cur若是最近公共祖先,p,q节点一定在cur的两边,说明找到了
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode cur = root;
		while(cur != null) {
			if(cur.val > p.val && cur.val > q.val) { // 说明要找到节点在左子树中
				cur = cur.left;
			} else if(cur.val < p.val && cur.val < q.val) { // 说明要找的节点在右子树中
				cur = cur.right;
			} else {
				return cur;
			}
		}
		return cur;
    }
}
复杂度分析:
在这里插入图片描述

2.3 二叉树的最近公共祖先

题目链接:

剑指 Offer 68 - II. 二叉树的最近公共祖先

题意:
  • 与上题类似,只不过给定的结果是二叉树,不是带有顺序的二叉搜索树
题解:
在这里插入图片描述
  • 整体解决思路:

    • 二叉树进行深度先序遍历寻找p,q,当遇到节点 p或 q 时返回
    • 从底至顶回溯,当节点 p, q 在节点 root的异侧时,节点 root即为最近公共祖先,则向上返回 root 。
  • 解题步骤:

    • 1.递归终止条件是:遍历的节点为null或p,q均返回当前节点
    • 2.依次遍历左,右子树,并记录返回的节点值
    • 3.对左右子树返回的结果进行判断
      • 如果左右子树返回结果均为null,说明此root节点子树中没有p,q,将null返回
      • 如果左右子树有一个子树找到了,不为null,说明target在此子树中,将此子节点往上回溯
      • 如果左右子树均不为null,说明p,q在两侧,此节点就是要找的target,将此节点往上回溯
// 二叉树的最近公共祖先
	public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//		- 1.递归终止条件是:遍历的节点为null或p,q均返回当前节点
		if(root == null || root == p || root == q) return root;
		
//		- 2.依次遍历左,右子树,并记录返回的节点值
		TreeNode left = lowestCommonAncestor(root.left, p, q);
		TreeNode right = lowestCommonAncestor(root.right, p, q);
		
//		- 3.对左右子树返回的结果进行判断
//		  - 如果左右子树返回结果均为null,说明此root节点子树中没有p,q,将null返回
//		  - 如果左右子树有一个子树找到了,不为null,说明target在此子树中,将此子节点往上回溯
		if(left == null) return right;
		if(right == null) return left;
		
//		  - 如果左右子树均不为null,说明p,q在两侧,此节点就是要找的target,将此节点往上回溯
		return root;		
	}
复杂度分析:
  • 时间复杂度 O(N) : 其中 N 为二叉树节点数;最差情况下,需要递归遍历树的所有节点。
  • 空间复杂度 O(N) : 最差情况下,递归深度达到 N ,系统使用 O(N) 大小的额外空间
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 二叉树的递归遍历二叉树的递归遍历是指通过递归方法遍历二叉树的各个节点,按照某种次序访问每个节点。常见的二叉树遍历方式有前序遍历、中序遍历和后序遍历。 2. 二叉树的非递归遍历二叉树的非递归遍历是指通过循环等非递归方法遍历二叉树的各个节点,按照某种次序访问每个节点。非递归遍历需要借助栈来实现,常见的二叉树遍历方式有前序遍历、中序遍历和后序遍历。 3. 二叉树的层次遍历二叉树的层次遍历是指按照从上到下、从左到右的顺序遍历每一层节点。常用的方法是使用队列来实现,首先将根节点入队列,然后依次出队列,并将其左右子节点入队列,直到队列为空。 4. 输出二叉树上所有叶节点: 二叉树上的叶节点是指没有子节点的节点。可以通过递归方式,对每个节点进行判断,如果该节点没有左右子节点,则将该节点输出。 5. 求二叉树的高度: 二叉树的高度是指从根节点到叶节点最长路径上经过的边数。可以通过递归方式求解,从左右子树中选取较大的一个加上根节点即可。 6. 二叉树层序生成算法二叉树层序生成算法是指按照从上到下、从左到右的顺序依次生成每个节点。可以使用队列来实现,首先将根节点入队列,然后依次出队列,并根据当前节点生成其左右子节点,将其入队列,直到生成完所有节点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值