二叉树遍历算法总结

文章目录

    • 前提要素
    • 深度优先搜索DFS
      • 经典遍历算法
        • 前序遍历
          • 递归版
          • 迭代版
        • 中序遍历
          • 递归版
          • 迭代版
        • 后序遍历
          • 递归版
          • 迭代版
      • Morris遍历算法
        • 中序遍历
        • 前序遍历
        • 后序遍历
    • 广度优先搜索BFS
      • 按层遍历
    • 参考资料

前提要素

本文代码用Java实现。

//二叉树节点结构
public static class TreeNode {public int val;public TreeNode left;public TreeNode right;public TreeNode(int x) {val = x;}public TreeNode(int val, TreeNode left, TreeNode right) {this.val = val;this.left = left;this.right = right;}
}//打印二叉树节点值
private static void print(TreeNode node, StringBuilder sb) {sb.append(node.val);sb.append(',');
}

深度优先搜索DFS

经典遍历算法

在这里插入图片描述
Depth-first traversal (dotted path) of a binary tree:

  • Pre-order (node access at position red color dot):
    • F, B, A, D, C, E, G, I, H;
  • In-order (node access at position green color dot):
    • A, B, C, D, E, F, G, H, I;
  • Post-order (node access at position blue color dot):
    • A, C, E, D, B, H, I, G, F.

前序遍历

递归版
public static String recursivePreorderTraverse(TreeNode root) {if(root == null) return "";StringBuilder sb = new StringBuilder();recursivePreorderTraverse(root, sb);return sb.substring(0, sb.length() - 1);
}private static void recursivePreorderTraverse(TreeNode node, StringBuilder sb) {if (node == null)return;print(node, sb);recursivePreorderTraverse(node.left, sb);recursivePreorderTraverse(node.right, sb);
}
迭代版
public static String iterativePreorderTraverse(TreeNode root) {if(root == null)return "";StringBuilder sb = new StringBuilder();LinkedList<TreeNode> stack = new LinkedList<>();stack.push(root);while(!stack.isEmpty()) {TreeNode node = stack.pop();print(node, sb);//right child is pushed first so that left is processed firstif(node.right != null)stack.push(node.right);if(node.left != null)stack.push(node.left);}return sb.substring(0, sb.length() - 1);
}

中序遍历

递归版
public static String recursiveInorderTraverse(TreeNode root) {if(root == null) return "";StringBuilder sb = new StringBuilder();recursiveInorderTraverse(root, sb);return sb.substring(0, sb.length() - 1);
}private static void recursiveInorderTraverse(TreeNode node, StringBuilder sb) {if (node == null)return;recursiveInorderTraverse(node.left, sb);print(node, sb);recursiveInorderTraverse(node.right, sb);
}
迭代版
public static String iterativeInorderTraverse(TreeNode root) {if(root == null) return "";StringBuilder sb = new StringBuilder();LinkedList<TreeNode> stack = new LinkedList<>();TreeNode p = root;while(!stack.isEmpty() || p != null) {if(p != null) {stack.push(p);p = p.left;}else {p = stack.pop();print(p, sb);p = p.right;}}		return sb.substring(0, sb.length() - 1);
}

后序遍历

递归版
public static String recursivePostorderTraverse(TreeNode root) {if(root == null) return "";StringBuilder sb = new StringBuilder();recursivePostorderTraverse(root, sb);return sb.substring(0, sb.length() - 1);
}public static void recursivePostorderTraverse(TreeNode node, StringBuilder sb) {if (node == null)return;recursivePostorderTraverse(node.left, sb);recursivePostorderTraverse(node.right, sb);print(node, sb);
}
迭代版
public static String iterativePostorderTraverse(TreeNode root) {if(root == null) return "";StringBuilder sb = new StringBuilder();LinkedList<TreeNode> stack = new LinkedList<>();TreeNode p = root, lastNodeVisited = null;while(!stack.isEmpty() || p != null) {if(p != null) {stack.push(p);p = p.left;}else {TreeNode peekNode = stack.peek();// if right child exists and traversing node// from left child, then move rightif(peekNode.right != null && lastNodeVisited != peekNode.right) {p = peekNode.right;}else {print(peekNode, sb);lastNodeVisited = stack.pop();}}}return sb.substring(0, sb.length() - 1);
}

Morris遍历算法

实现二叉树的前序(preorder)、中序(inorder)、后序(postorder)遍历有两个常用的方法:一是递归(recursive),二是使用栈实现的迭代版本(stack+iterative)。这两种方法都是O(n)的空间复杂度(递归本身占用stack空间或者用户自定义的stack)。

Morris Traversal方法与前两种方法的不同在于该方法只需要O(1)空间,而且同样可以在O(n)时间内完成

要使用O(1)空间进行遍历,最大的难点在于,遍历到子节点的时候怎样重新返回到父节点(假设节点中没有指向父节点的p指针),由于不能用栈作为辅助空间。为了解决这个问题,Morris方法用到了线索二叉树(threaded binary tree)的概念。在Morris方法中不需要为每个节点额外分配指针指向其前驱(predecessor)和后继节点(successor),只需要利用叶子节点中的左右空指针指向某种顺序遍历下的前驱节点或后继节点就可以了。

Morris只提供了中序遍历的方法,在中序遍历的基础上稍加修改可以实现前序遍历,后续遍历。

中序遍历

public String inorderTraverse(TreeNode root) {StringBuilder sb = new StringBuilder();inorderMorrisTraversal(root, sb);return sb.substring(0, sb.length() - 1);
}private void inorderMorrisTraversal(TreeNode root, StringBuilder sb) {TreeNode cur = root, prev = null;while (cur != null) {if (cur.left == null) {// 1.如果curr左子节点为空:print(cur.val, sb);// 输出当前节点cur = cur.right;// curr指向它的右子节点,为下次循环作准备} else {// 2. 如果curr左子节点不为空,在curr的左子树中,找到curr在中序遍历下的前驱节点。prev = cur.left;while (prev.right != null && prev.right != cur)prev = prev.right;// 2.a 如果前驱节点的右孩子为空,if (prev.right == null) {// (这里curr第一次遍历到,建立连接)prev.right = cur; // 将它的右子节点设置为curr。(让当前节点与中序遍历下的前驱节点形成链接,这里形成)cur = cur.left; // curr指向它的 左子节点,为下次循环作准备} else {// 2.b 如果前驱节点的右子节点 为curr,(这里curr第二次遍历到,断开连接,打印值)print(cur.val, sb);prev.right = null;// 将它的右孩子重新设为空(断开链接,恢复树的形状)。cur = cur.right; // curr指向它的 左子节点,为下次循环作准备}}}
}

前序遍历

public String preorderTraverse(TreeNode root) {StringBuilder sb = new StringBuilder();preorderMorrisTraversal(root, sb);return sb.substring(0, sb.length() - 1);
}private void preorderMorrisTraversal(TreeNode root, StringBuilder sb) {TreeNode cur = root, prev = null;while (cur != null) {if (cur.left == null) {print(cur.val, sb);cur = cur.right;} else {prev = cur.left;while (prev.right != null && prev.right != cur)prev = prev.right;if (prev.right == null) {print(cur.val, sb); // the only difference with inorder-traversalprev.right = cur;cur = cur.left;} else {prev.right = null;cur = cur.right;}}}
}

后序遍历

public String postorderTraverse(TreeNode root) {StringBuilder sb = new StringBuilder();postorderMorrisTraversal(root, sb);return sb.substring(0, sb.length() - 1);
}private void postorderMorrisTraversal(TreeNode root, StringBuilder sb) {TreeNode dump = new TreeNode(0);dump.left = root;TreeNode cur = dump, prev = null;while (cur != null) {if (cur.left == null) {cur = cur.right;} else {prev = cur.left;while (prev.right != null && prev.right != cur)prev = prev.right;if (prev.right == null) {prev.right = cur;cur = cur.left;} else {// call printprintReverse(cur.left, prev, sb); prev.right = null;cur = cur.right;}}}
}// print the reversed tree nodes 'from' -> 'to'
private void printReverse(TreeNode from, TreeNode to, StringBuilder sb) {reverse(from, to);TreeNode p = to;while (true) {print(p.val, sb);if (p == from)break;p = p.right;}reverse(to, from);
}// reverse the tree nodes 'from' -> 'to'.
private void reverse(TreeNode from, TreeNode to) {if (from == to)return;TreeNode x = from, y = from.right, z;while (true) {z = y.right;y.right = x;x = y;y = z;if (x == to)break;}
}

广度优先搜索BFS

按层遍历

在这里插入图片描述
Level-order: F, B, G, A, D, I, C, E, H.

public static String levelOrderTraverse(TreeNode root) {if(root == null) return "";StringBuilder sb = new StringBuilder();LinkedList<TreeNode> queue = new LinkedList<>();queue.offer(root);while(!queue.isEmpty()) {TreeNode node = queue.poll();print(node, sb);if(node.left != null)queue.offer(node.left);if(node.right != null)queue.offer(node.right);}return sb.substring(0, sb.length() - 1);
}

参考资料

  1. Tree traversal - Wikipedia
  2. Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间) - AnnieKim - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445624.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线段树简单实现

首先&#xff0c;线段树是一棵满二叉树。&#xff08;每个节点要么有两个孩子&#xff0c;要么是深度相同的叶子节点&#xff09; 每个节点维护某个区间&#xff0c;根维护所有的。 如图&#xff0c;区间是二分父的区间。 当有n个元素&#xff0c;初始化需要o(n)时间&#xf…

树状数组实现

树状数组能够完成如下操作&#xff1a; 给一个序列a0-an 计算前i项和 对某个值加x 时间o(logn) 注意&#xff1a;有人觉得前缀和就行了&#xff0c;但是你还要维护啊&#xff0c;改变某个值&#xff0c;一个一个改变前缀和就是o(n)了。 线段树树状数组的题就是这样&#x…

KMP子字符串匹配算法学习笔记

文章目录学习资源什么是KMP什么是前缀表为什么一定要用前缀表如何计算前缀表前缀表有什么问题使用next数组来匹配放码过来构造next数组一、初始化二、处理前后缀不相同的情况三、处理前后缀相同的情况使用next数组来做匹配代码总览测试代码时间复杂度分析学习资源 字符串&…

内存分区

之前一直比较懵&#xff0c;想想还是单独写一个短篇来记录吧 一般内存主要分为&#xff1a;代码区、常量区、静态区&#xff08;全局区&#xff09;、堆区、栈区这几个区域。 代码区&#xff1a;存放程序的代码&#xff0c;即CPU执行的机器指令&#xff0c;并且是只读的。 常…

数据结构课上笔记5

介绍了链表和基本操作 用一组物理位置任意的存储单元来存放线性表的数据元素。 这组存储单元既可以是连续的&#xff0c;也可以是不连续的&#xff0c;甚至是零散分布在内存中的任意位置上的。因此&#xff0c;链表中元素的逻辑次序和 物理次序不一定相同。 定义&#xff1a; …

Java设计模式(2 / 23):观察者模式

定义 观察者&#xff08;Observer&#xff09;模式定义了对象之间的一对多依赖&#xff0c;这样一来&#xff0c;当一个对象改变状态时&#xff0c;它的所有依赖者都会收到通知并自动更新。 OO设计原则&#xff1a;为了交互对象之间的松耦合设计而努力。 案例&#xff1a;气…

二叉树概述

各种实现和应用以后放链接 一、二叉树的基本概念 二叉树&#xff1a;二叉树是每个节点最多有两个子树的树结构。 根节点&#xff1a;一棵树最上面的节点称为根节点。 父节点、子节点&#xff1a;如果一个节点下面连接多个节点&#xff0c;那么该节点称为父节点&#xff0c;它…

Java设计模式(1 / 23):策略模式

定义 策略&#xff08;Strategy&#xff09;模式定义了算法族&#xff0c;分别封装起来&#xff0c;让它们之间可以互相替换 &#xff0c;此模式让算法的变化独立于使用算法的客户。 案例&#xff1a;模拟鸭子应用 一开始 新需求&#xff1a;模拟程序需要会飞的鸭子 在父类新…

Java设计模式(3 / 23):装饰者模式

文章目录定义案例1&#xff1a;三点几啦首次尝试再次尝试设计原则&#xff1a;类应该对扩展开放&#xff0c;对修改关闭尝用装饰者模式装饰者模式特征本例的类图放码过来饮料类HouseBlendDarkRoastEspressoDecaf调料装饰类MilkMochaSoyWhip运行测试类案例2&#xff1a;编写自己…

c语言知识体系

原文&#xff1a;https://blog.csdn.net/lf_2016/article/details/80126296#comments

《游戏编程入门 4th》笔记(1 / 14):Windows初步

文章目录Windows编程概述获取Windows理解Windows消息机制多任务多线程事件处理DirectX快速概览Direct3D是什么Window程序基础创建第一个Win32项目理解WinMainWinMain函数调用完整的WinMainGetMessage函数调用寻求帮助Windows编程概述 DirectX&#xff0c;流行的游戏编程库。它…

《游戏编程入门 4th》笔记(2 / 14):监听Windows消息

文章目录编写一个Windows程序理解InitInstanceInitInstance函数调用InitInstance的结构理解MyRegisterClassMyRegisterClass函数调用MyRegisterClass的作用揭露WinProc的秘密WinProc函数调用WinProc的大秘密什么是游戏循环The Old WinMain对持续性的需要实时终止器WinMain和循环…

数据结构课上笔记6

本节课介绍了单链表的操作实现细节&#xff0c;介绍了静态链表。 链表带头的作用&#xff1a;对链表进行操作时&#xff0c;可以对空表、非空表的情况以及 对首元结点进行统一处理&#xff0c;编程更方便。 下面给出带头的单链表实现思路&#xff1a; 按下标查找&#xff1a; …

17校招真题题集(3)11-15

注&#xff1a;本系列题目全是按照通过率降序来排列的&#xff0c;基本保证题目难度递增。 11、 题目名称&#xff1a;买苹果 来源&#xff1a;网易 题目描述 小易去附近的商店买苹果&#xff0c;奸诈的商贩使用了捆绑交易&#xff0c;只提供6个每袋和8个每袋的包装(包装不…

QT5生成.exe文件时,出现缺少QT5core.dll文件解决方法

在 http://qt-project.org/downloads 下载Qt SDK安装需要Qt版本。在QtCreator下&#xff0c;程序可以正常运行&#xff0c;但是当关闭QtCreator后&#xff0c;在DeBug目录下再运行相应的*.exe程序时&#xff0c;会提示缺少Qt5Core.dll错误。解决方法&#xff1a;添加电脑环境变…

《基于Java实现的遗传算法》笔记(7 / 7):个人总结

文章目录为何采用遗传算法哪些问题适合用遗传算法解决遗传算法基本术语一般遗传算法的过程基本遗传算法的伪代码为何采用遗传算法 遗传算法是机器学习的子集。在实践中&#xff0c;遗传算法通常不是用来解决单一的、特定问题的最好算法。对任何一个问题&#xff0c;几乎总有更…

Java设计模式(4 / 23):单例模式

文章目录单例模式的应用场景饿汉式单例模式懒汉式单例模式改进&#xff1a;synchronized改进&#xff1a;双重检查锁改进&#xff1a;静态内部类破坏单例用反射破坏单例用序列化破坏单例解密注册式单例模式枚举式单例模式解密容器式单例线程单例实现ThreadLocal单例模式小结参考…

约瑟夫环-(数组、循环链表、数学)

约瑟夫环&#xff08;约瑟夫问题&#xff09;是一个数学的应用问题&#xff1a;已知n个人&#xff08;以编号1&#xff0c;2&#xff0c;3...n分别表示&#xff09;围坐在一张圆桌周围。从编号为k的人开始报数&#xff0c;数到m的那个人出列&#xff1b;他的下一个人又从1开始报…

链表相交问题

本来想自己写&#xff0c;写了一半发现一篇文章&#xff0c;解释写得简单易懂&#xff0c;我就直接拿过来了。 这个问题值得反复地写&#xff0c;锻炼链表coding能力的好题。 //如果两个链表都不带环 int NotCycleCheckCross(pLinkNode head1,pLinkNode head2) {pLinkNode lis…

双栈

利用栈底位置相对不变的特性&#xff0c;可以让两个顺序栈共享一个空间。 具体实现方法大概有两种&#xff1a; 一种是奇偶栈&#xff0c;就是所有下标为奇数的是一个栈&#xff0c;偶数是另一个栈。但是这样一个栈的最大存储就确定了&#xff0c;并没有起到互补空缺的作用&a…