文章目录

  • 二叉树的遍历
    • 一、深度优先遍历
      • 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;}}
}

二、非遍历问题解决模板

本模板并不适用于所有的二叉树问题。适用于再二叉树递归时,无需考虑左右子树的具体情况,只需对左右子树所提供的某一特定信息进行操作,并进行判断解决。

流程大致如下:

  1. 罗列问题可能的所有情况
  2. 罗列判断标准
  3. 确定所需信息
  4. 获取左右子树判断信息
  5. 对左右子树的信息进行判断
  6. 根据左右子树的信息结合本节点情况,返回本子树的信息
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
    • 左右子树均不为平衡二叉树
    • 左右子树仅一个为平衡二叉树
  2. 罗列判断标准
    • 该节点左子树为平衡二叉树
    • 该节点右子树为平衡二叉树
    • 左右子树高度差小于等于1
  3. 确定所需信息
    • 该子树是否为平衡二叉树
    • 该子树的高度
  4. 获取左右子树判断信息
  5. 依据判断标准对左右子树的信息进行判断
  6. 根据左右子树的信息结合本节点情况,返回本子树的信息
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);
}

依据该模板对于本问题可以得出以下分析:

  1. 罗列问题可能的所有情况

    • 左右子树均为二叉搜索树,且左右子树于本节点满足二叉搜索树要求
    • 左右子树均为二叉搜索树,但左右子树于本节点不满足二叉搜索树要求
    • 左右子树均不为二叉搜索树
  2. 罗列判断标准
    • 左右子树均为二叉搜索树
    • 左子树最大值小于本节点
    • 右子树最小值大于本节点
  3. 确定所需信息
    • 是否为二叉搜索树
    • 左子树最大值
    • 右子树最小值
  4. 获取左右子树判断信息
  5. 依据判断标准对左右子树的信息进行判断
  6. 根据左右子树的信息结合本节点情况,返回本子树的信息
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;
}

二叉树的遍历及解题思路相关推荐

  1. Leetcode 107. 二叉树的层次遍历 II 解题思路及C++实现

    解题思路: 使用队列实现二叉树的层序遍历,因为题目中要求每一层的val存储在一个vector中,所以在内循环中,还需要一个队列,用以存储更新每一层的节点. 在最后,需要将得到的res数组逆序. /** ...

  2. Leetcode 199. 二叉树的右视图 解题思路及C++实现

    解题思路: 使用层序遍历,得到每一层的最后边的节点,然后push_back到结果容器中. 通过使用q.size()函数,只需要一个队列即可实现层序遍历. /*** Definition for a b ...

  3. Leetcode 114. 二叉树展开为链表 解题思路及C++实现

    解题思路: 使用递归的方法.根结点的左子树中,最右边的节点是左子树成为链表后的最后一个节点,找到这个节点,然后把这个节点的右节点指向根结点的右子树,同时,将根结点右子节点指向其左子节点,根结点的左子节 ...

  4. Leetcode 257. 二叉树的所有路径 解题思路及C++实现

    解题思路: 使用深度优先搜索(DFS),深度优先搜索的终止条件是:当前节点root为叶子节点,即:!root->left && !root->right 为真,则找到了一条 ...

  5. 【URAL 1136 --- Parliament】二叉树的遍历

    [URAL 1136 --- Parliament]二叉树的遍历 Description A new parliament is elected in the state of MMMM. Each ...

  6. 二叉树----层序遍历

    1.层序遍历 层序遍历:层序遍历即逐层按顺序遍历二叉树的各个节点,故层序遍历又叫广度优先遍历. 如图:广度优先遍历即按ABCDEFGH的顺序遍历 2.解题思路 1.这里我们利用队列先进先出的结构特点, ...

  7. Leetcode 105. 从前序与中序遍历序列构造二叉树 解题思路及C++实现

    解题思路: 前序遍历preorder中,第一个即为根节点,然后找到中序遍历inorder中对应的节点,则inorder中该节点之前的值均在根节点的左子树上,该节点后面的值都在根节点的右子树上,所以可以 ...

  8. 二叉树深度优先遍历解题思路

    文章目录 1.二叉树深度优先遍历解题思路 1.1.三种深度优先遍历的方式 1.2.深度优先遍历的启示 1.2.1.递归形成条件 1.2.2递归过程的实际工作顺序 1.2.2.1.单路递归的实际工作顺序 ...

  9. Leetcode 106. 从中序与后序遍历序列构造二叉树 解题思路及C++实现

    解题思路: 思路和Leetcode 105题相同.区别在于,在这一题中,后序遍历的最后一个值为根节点. 然后仍然是找到根节点后,划分左右子树,递归构建. /*** Definition for a b ...

最新文章

  1. jmeter异步请求测试怎么测试_JMeter接口测试之HTTP请求默认值
  2. Shell中要如何调用别的shell脚本
  3. 注册界面的设计与实现
  4. redhat搭建php环境,rhel5搭建PHP5.2.6+apache2.2.9开发环境(模块全)
  5. 使用RMAN连接到数据库
  6. Firefox 10正式发布
  7. python心得-基本概念2
  8. linux 串口格式化输出字符串,如何把电压这个浮点值转字符串输出到串口
  9. mysql几种语言_mysql的几种SQL语句
  10. qt designer 信号与槽_Qt信号与槽,一直无法执行自定义槽函数,是什么原因
  11. Windows Azure AppFabric应用程序
  12. Rational Team Concert 的使用
  13. 推荐几个常用的Python扩展工具包
  14. win7右下角显示此windows副本不是正版
  15. s2系列——s2-012,s2-013,s2-015,s2-016
  16. 8.MIL中相机标定(Calibration)
  17. 关于Numpy数组中属性shape的理解
  18. java web 常见框架
  19. Clone Graph问题及解法
  20. zoomlt屏幕放大画画工具

热门文章

  1. ns3—— sixth.cc 大概理解
  2. 定点乘法器优化---华为杯
  3. deepin安装卡死在蓝色背景_deepin官方论坛-深度科技官网旗下网站
  4. python权限管理设置_python权限管理框架
  5. jQuery颜色选择器ColorPicker
  6. 基于51单片机的霓虹灯c语言,基于51单片机霓虹灯.pdf
  7. 第十二章:项目采购管理 - (12.0 什么是项目采购管理)
  8. AirPods站起来,千万个Hi-Fi倒下去
  9. 大家好,给大家介绍一下,这是金蛇郎君
  10. 什么是物联网?常见 IoT 物联网协议最全讲解