二叉树的遍历及解题思路
文章目录
- 二叉树的遍历
- 一、深度优先遍历
- 1. 先序遍历
- (1) 递归形式
- (2) 非递归形式
- 2. 中序遍历
- (1) 递归形式
- (2) 非递归形式
- 3. 后序遍历
- (1) 递归形式
- (2) 非递归形式
- 二、广度优先遍历
- 三、Morris遍历
- 1. Morris先序遍历
- 2. Morris中序遍历
- 3. Morris后序遍历
- 二叉树问题解决思路
- 一、 遍历解决
- 二、非遍历问题解决模板
本篇文章参考于左程云算法课程,部分详细讲解于左程云算法课程视频
二叉树的遍历
在解决二叉树问题时,很多时候需要对二叉树进行遍历,根据遍历的结果进而解决问题。因此,二叉树的遍历对解决二叉树相关问题显得尤为重要。二叉树的遍历方法大致分为三种:深度优先遍历、广度优先遍历、Morris遍历。
一、深度优先遍历
深度优先遍历分为:先序遍历、中序遍历、后序遍历,二对于这三种遍历方式又分别有递归形式与非递归形式的实现。
1. 先序遍历
先序遍历的流程为:
- 访问本节点
- 访问左孩子
- 访问右孩子
(1) 递归形式
public void preOrderRecur(Node head){if(head==null) return;System.out.print(head.value);preOrderRecur(head.left);preOrderRecur(head.right);
}
(2) 非递归形式
public void preOrderNotRecur(Node head){if(head!=null){Stack<Node> stack=new Stack<Node>();stack.add(head);while(!stack.isEmpty()){head=stack.pop();System.out.print(head.value);if(head.right!=null) stack.push(head.riight);if(head.left!=null) stack.push(head.left);}}
}
非递归形式先序遍历的主要思想为:
- 将本节点(头节点)入栈
- 栈顶元素出栈,记为curNode
- 将curNode右孩子入栈
- 将curNode左孩子入栈
- 循环入栈、出栈过程,直至栈空
2. 中序遍历
先序遍历的流程为:
- 访问左孩子
- 访问本节点
- 访问右孩子
(1) 递归形式
public void inOrderRecur(Node head){if(head==null) return;preOrderRecur(head.left);System.out.print(head.value);preOrderRecur(head.right);
}
(2) 非递归形式
public void inOrderNotRecur(Node root){Stack<Node> stack=new Stack<>();while (root!=null||!stack.isEmpty()){if (root!=null){stack.push(root);root=root.left;}else {root=stack.pop();System.out.println(root.val);root=root.right;}}
}
非递归形式中序遍历的主要思想为:
若当前节点curNode非空,则入栈
不断将curNode左孩子,及其左子孙节点入栈,直至左子孙节点为空
当左子孙节点为空,栈顶元素出栈即为curNode
寻找curNode右子树
循环流程,直至栈空
3. 后序遍历
先序遍历的流程为:
- 访问左孩子
- 访问右孩子
- 访问本节点
(1) 递归形式
public void posOrderRecur(Node head)if(head==null) return;preOrderRecur(head.left);preOrderRecur(head.right);System.out.print(head.value);
}
(2) 非递归形式
public void posOrderNotRecur(Node root){if (root!=null){Stack<Node> stack=new Stack<>();stack.push(root);Node curNode=null;while (!stack.isEmpty()){curNode=stack.peek();if (curNode.left!=null&&root!=curNode.left&&root!=curNode.right) stack.push(curNode.left);else if (curNode.right!=null&&root!=curNode.right) stack.push(curNode.right);else {System.out.println(stack.pop().val);root=curNode;}}}
}
非递归形式后序遍历的主要思想为:
- 根节点入栈
- 若栈顶元素curNode左孩子不为空且最新出栈元素不是curNode的左右子元素
- curNode左孩子入栈
- 若栈顶元素curNode右孩子不为空且最新出栈元素不是粗人Node的右孩子
- curNode右孩子入栈
- 否则,栈顶元素出栈
- 循环入栈、出栈直至栈空
二、广度优先遍历
广度优先遍历即是按二叉树的层级遍历每个层级的元素
public void levelOrder(Node root){if (root!=null){Queue<Node> queue=new LinkedList<>();queue.add(root);while (!queue.isEmpty()){root=queue.poll();System.out.println(root.val);if (root.left!=null) queue.add(root.left);if (root.right!=null) queue.add(root.right);}}
}
层序遍历的主要思想为:
- 根节点入队
- 队首元素出队记为curNode
- curNode左孩子不为空,将curNode左孩子入队
- curNode右孩子不为空,将curNode右孩子入队
- 循环入队、出队,直至队空
三、Morris遍历
Morris遍历相较与广度优先遍历和深度优先遍历,显著的降低了空间复杂度和时间复杂度。因此,当优化某个需将二叉树遍历遍历的算法时,可选择Morris遍历。由于Morris遍历时改变了某些节点的指向,因此该遍历方法不宜用于不能改变二叉树内容的问题。
public void morris(Node head){if(head==null) return;Node cur=head;Node mostRight=null;while(cur!=null){mostRight=cur.left;//mostRight为cur的左孩子if(mostRight!=null){//cur存在左子树while(mostRight.right!=null&&mostRight.right!=cur){//获取左子树最右节点mostRight=mostRight.right;}if(mostRight.right==null){//第一次到curmostRight.right=cur;cur=cur.left;continue;}else{//mostRight.right==cur即是第二次到curmostRight.right=null;}}cur=cur.right;}
}
Morris遍历的本质实际为实现了线索二叉树。
Morris遍历的主要思想为:(假定cur为头节点)
- 如果cur没有左孩子,cur向右移动(cur=cur.right)
- 如果cur有左孩子,找到其左子树上最右的节点mostRight;
- 如果mostRight的右指针指向null,则让该节点右指针指向cur,然后cur向左移动(cur=cur.left)
- 如果mostRight的右指针指向cur,则让该节点右指针指向null,然后cur向右移动(cur=cur.right)
- cur为空时遍历停止
1. Morris先序遍历
public void morrisPreOrder(Node head){if(head==null) return;Node cur=head;Node mostRight=null;while(cur!=null){mostRight=cur.left;//mostRight为cur的左孩子if(mostRight!=null){//cur存在左子树while(mostRight.right!=null&&mostRight.right!=cur){//获取左子树最右节点mostRight=mostRight.right;}if(mostRight.right==null){//第一次到curSystem.out.print(cur.value);mostRight.right=cur;cur=cur.left;continue;}else{//mostRight.right==cur即是第二次到curmostRight.right=null;}}else{//cur不存在左子树System.out.print(cur.value);}cur=cur.right;}
}
Morris先序遍历的思想为:在Morris遍历的基础上,无论能够到达当前节点一次或两次,均在首次到达时打印。
2. Morris中序遍历
public void morrisInOrder(Node head){if(head==null) return;Node cur=head;Node mostRight=null;while(cur!=null){mostRight=cur.left;//mostRight为cur的左孩子if(mostRight!=null){//cur存在左子树while(mostRight.right!=null&&mostRight.right!=cur){//获取左子树最右节点mostRight=mostRight.right;}if(mostRight.right==null){//第一次到curmostRight.right=cur;cur=cur.left;continue;}else{//mostRight.right==cur即是第二次到curmostRight.right=null;}}System.out.print(cur.value);cur=cur.right;}
}
Morris先序遍历的思想为:在Morris遍历的基础上
- 当某个节点只能到达一次是,直接打印
- 当某个节点能到达两次时,在第二次到达时打印
3. Morris后序遍历
public void printEdge(Node X){//逆序打印以X为头的树的右边界Node tail=reverseEdge(X);Node cur=tail;while(cur!=null){System.out.print(cur.value);cur=cur.right;}reverseEdge(tail);
}
public Node reverseEdge(Node from){//将右边界组成的链表逆向Node pre=null;Node next=null;while(from!=null){next=from.right;from.right=pre;pre=from;from=next;}return pre;
}
public void morrisPosOrder(Node head){if(head==null) return;Node cur=head;Node mostRight=null;while(cur!=null){mostRight=cur.left;//mostRight为cur的左孩子if(mostRight!=null){//cur存在左子树while(mostRight.right!=null&&mostRight.right!=cur){//获取左子树最右节点mostRight=mostRight.right;}if(mostRight.right==null){//第一次到curmostRight.right=cur;cur=cur.left;continue;}else{//mostRight.right==cur即是第二次到curmostRight.right=null;printEdge(cur.left);//逆序打印该节点左树的右边界}}cur=cur.right;}printEdge(head);//打印整棵树的右边界
}
Morris先序遍历的思想为:在Morris遍历的基础上,仅当能到达两次的节点且为第二次到达该节点,则逆序打印该节点左树的右边界,最后打印整棵树的右边界。
二叉树问题解决思路
在解决二叉树的相关问题时,首先可以考虑是否可以通过二叉树的遍历解决,而后再考虑是否可以通过分解问题的思路解决(将问题分解为多个子问题,利用递归解决)。
一、 遍历解决
针对与本题,不难想到可以利用二叉树的层序遍历解决
public List<Integer> rightSideView(TreeNode root) {List<Integer> list=new ArrayList<>();Queue<TreeNode> queue=new LinkedList<>();if (root==null) return list;queue.add(root);while (!queue.isEmpty()){int size=queue.size();for (int i=0;i<size;i++){TreeNode curNode=queue.poll();if (i==size-1){//该层最右侧元素list.add(curNode.val);}if (curNode.left!=null) queue.add(curNode.left);if (curNode.right!=null) queue.add(curNode.right);}}return list;
}![]()
对于本题,不难想到可以利用二叉树的先序遍历解决
public void preOrder(TreeNode root,ArrayList<TreeNode> list){if (root==null) return;list.add(root);preOrder(root.left,list);preOrder(root.right,list);
}
public void flatten(TreeNode root) {ArrayList<TreeNode> nodeList=new ArrayList<>();preOrder(root,nodeList);if (nodeList.size()>0) {TreeNode curNode = nodeList.get(0);for (int i = 1; i < nodeList.size(); i++) {TreeNode temp = nodeList.get(i);curNode.left = null;curNode.right = temp;curNode = temp;}}
}
二、非遍历问题解决模板
本模板并不适用于所有的二叉树问题。适用于再二叉树递归时,无需考虑左右子树的具体情况,只需对左右子树所提供的某一特定信息进行操作,并进行判断解决。
流程大致如下:
- 罗列问题可能的所有情况
- 罗列判断标准
- 确定所需信息
- 获取左右子树判断信息
- 对左右子树的信息进行判断
- 根据左右子树的信息结合本节点情况,返回本子树的信息
public void fun(){process(root);
}
public InfoType process(TreeNode root){if(root==null) {//当节点为null时,返回的信息}InfoType left=process(root.left);//从左子树获取信息InfoType right=process(root.right);//从右子树获取信息//根据left、right信息对问题的标准进行判断return new InfoType();//根据左右子树的信息返回该节点的信息
}
以判断平衡二叉树为例:
- 罗列问题可能的所有情况
- 左右子树均为平衡二叉树,但左右子树高度差大于1
- 左右子树均不为平衡二叉树
- 左右子树仅一个为平衡二叉树
- 罗列判断标准
- 该节点左子树为平衡二叉树
- 该节点右子树为平衡二叉树
- 左右子树高度差小于等于1
- 确定所需信息
- 该子树是否为平衡二叉树
- 该子树的高度
- 获取左右子树判断信息
- 依据判断标准对左右子树的信息进行判断
- 根据左右子树的信息结合本节点情况,返回本子树的信息
class ReturnType{public boolean isBalanced;public int height;public ReturnType(boolean isB,int hei){isBalanced=isB;height=hei;}
}
public boolean isBalanced(TreeNode root){return process(root).isBalanced;
}
public ReturnType process(TreeNode root){if(root==null) return new ReturnType(true,0);ReturnType leftData=process(root.left);ReturnType rightData=process(root.right);int height=Math.max(leftData.height,right.height)+1;boolean isBalanced=leftData.isBalanced && rightData.isBalanced && Math.abs(leftData.height-right.height)<2;return new ReturnType(isBalanced,height);
}
依据该模板对于本问题可以得出以下分析:
- 罗列问题可能的所有情况
- 左右子树均为二叉搜索树,且左右子树于本节点满足二叉搜索树要求
- 左右子树均为二叉搜索树,但左右子树于本节点不满足二叉搜索树要求
- 左右子树均不为二叉搜索树
- 罗列判断标准
- 左右子树均为二叉搜索树
- 左子树最大值小于本节点
- 右子树最小值大于本节点
- 确定所需信息
- 是否为二叉搜索树
- 左子树最大值
- 右子树最小值
- 获取左右子树判断信息
- 依据判断标准对左右子树的信息进行判断
- 根据左右子树的信息结合本节点情况,返回本子树的信息
public class Info{public boolean isSearch;public Long max;//左子树最大值public Long min;//右子树最小值public Info(boolean isSearch,Long max,Long min){this.isSearch=isSearch;this.max=max;this.min=min;}
}
public Info process(TreeNode root){if (root==null) return new Info(true,Long.MIN_VALUE,Long.MAX_VALUE);Info left=process(root.left);Info right=process(root.right);boolean isSearch=false;Long max= Math.max(root.val, right.max);Long min= Math.min(root.val, left.min);if (left.isSearch&& right.isSearch&& root.val> left.max&&root.val< right.min){isSearch=true;return new Info(isSearch,max,min);}else return new Info(isSearch,max,min);
}
public boolean isValidBST(TreeNode root) {return process(root).isSearch;
}
二叉树的遍历及解题思路相关推荐
- Leetcode 107. 二叉树的层次遍历 II 解题思路及C++实现
解题思路: 使用队列实现二叉树的层序遍历,因为题目中要求每一层的val存储在一个vector中,所以在内循环中,还需要一个队列,用以存储更新每一层的节点. 在最后,需要将得到的res数组逆序. /** ...
- Leetcode 199. 二叉树的右视图 解题思路及C++实现
解题思路: 使用层序遍历,得到每一层的最后边的节点,然后push_back到结果容器中. 通过使用q.size()函数,只需要一个队列即可实现层序遍历. /*** Definition for a b ...
- Leetcode 114. 二叉树展开为链表 解题思路及C++实现
解题思路: 使用递归的方法.根结点的左子树中,最右边的节点是左子树成为链表后的最后一个节点,找到这个节点,然后把这个节点的右节点指向根结点的右子树,同时,将根结点右子节点指向其左子节点,根结点的左子节 ...
- Leetcode 257. 二叉树的所有路径 解题思路及C++实现
解题思路: 使用深度优先搜索(DFS),深度优先搜索的终止条件是:当前节点root为叶子节点,即:!root->left && !root->right 为真,则找到了一条 ...
- 【URAL 1136 --- Parliament】二叉树的遍历
[URAL 1136 --- Parliament]二叉树的遍历 Description A new parliament is elected in the state of MMMM. Each ...
- 二叉树----层序遍历
1.层序遍历 层序遍历:层序遍历即逐层按顺序遍历二叉树的各个节点,故层序遍历又叫广度优先遍历. 如图:广度优先遍历即按ABCDEFGH的顺序遍历 2.解题思路 1.这里我们利用队列先进先出的结构特点, ...
- Leetcode 105. 从前序与中序遍历序列构造二叉树 解题思路及C++实现
解题思路: 前序遍历preorder中,第一个即为根节点,然后找到中序遍历inorder中对应的节点,则inorder中该节点之前的值均在根节点的左子树上,该节点后面的值都在根节点的右子树上,所以可以 ...
- 二叉树深度优先遍历解题思路
文章目录 1.二叉树深度优先遍历解题思路 1.1.三种深度优先遍历的方式 1.2.深度优先遍历的启示 1.2.1.递归形成条件 1.2.2递归过程的实际工作顺序 1.2.2.1.单路递归的实际工作顺序 ...
- Leetcode 106. 从中序与后序遍历序列构造二叉树 解题思路及C++实现
解题思路: 思路和Leetcode 105题相同.区别在于,在这一题中,后序遍历的最后一个值为根节点. 然后仍然是找到根节点后,划分左右子树,递归构建. /*** Definition for a b ...
最新文章
- jmeter异步请求测试怎么测试_JMeter接口测试之HTTP请求默认值
- Shell中要如何调用别的shell脚本
- 注册界面的设计与实现
- redhat搭建php环境,rhel5搭建PHP5.2.6+apache2.2.9开发环境(模块全)
- 使用RMAN连接到数据库
- Firefox 10正式发布
- python心得-基本概念2
- linux 串口格式化输出字符串,如何把电压这个浮点值转字符串输出到串口
- mysql几种语言_mysql的几种SQL语句
- qt designer 信号与槽_Qt信号与槽,一直无法执行自定义槽函数,是什么原因
- Windows Azure AppFabric应用程序
- Rational Team Concert 的使用
- 推荐几个常用的Python扩展工具包
- win7右下角显示此windows副本不是正版
- s2系列——s2-012,s2-013,s2-015,s2-016
- 8.MIL中相机标定(Calibration)
- 关于Numpy数组中属性shape的理解
- java web 常见框架
- Clone Graph问题及解法
- zoomlt屏幕放大画画工具