二叉树遍历算法总结

文章目录

    • 前提要素
    • 深度优先搜索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,一经查实,立即删除!

相关文章

时间复杂度 P/NP/NPC

你会经常看到网上出现“这怎么做&#xff0c;这不是NP问题吗”、“这个只有搜了&#xff0c;这已经被证明是NP问题了”之类的话。你要知道&#xff0c;大多数人此时所说的NP问题其实都是指的NPC问题。他们没有搞清楚NP问题和NPC问题的概念。NP问题并不是那种“只有搜才行”的问…

kmp1-HDU1711 HDU1686 HDU2087 HDU3746

HDU 1711 kmp模板题 http://acm.hdu.edu.cn/showproblem.php?pid1711 #include<stdio.h> #include<string.h> #define N 1000005 int s[N]; int p[N]; int next[N]; int m,n; void getnext(){int j0,k-1;next[0]-1;while(j<m){if(k-1||p[j]p[k]){j;k;next[j]…

kmp2-HDU1358 HUST1010 POJ2406 POJ2752

HDU1358 http://acm.hdu.edu.cn/showproblem.php?pid1358 先构造出 next[] 数组&#xff0c;下标为 i&#xff0c;定义一个变量 j i - next[i] 就是next数组下标和下标对应值的差&#xff0c;如果这个差能整除下标 i&#xff0c;即 i%j0 ,则说明下标i之前的字符串&#xff0…

18暑期培训总结

暑假一共直播讲了七次课&#xff0c;每次一小时到一个半小时&#xff0c;前六次讲解python主要实用语法&#xff0c;最后一次讲了学习方法和简单基础的思想和算法。由于时间有限&#xff0c;不能做到很好&#xff0c;请见谅。 学院做题网站&#xff1a;橙白oj http://oj.acm-i…

第七次课 课上代码

时间空间复杂度&#xff08;例子&#xff1a;1-n求和&#xff09; 复杂度&#xff1a;https://blog.csdn.net/hebtu666/article/details/82463970 https://blog.csdn.net/hebtu666/article/details/82465495 二分 一个数组查找某个值1 2 3 5 6 7 8 9 10 15 20。。 查找11 …

数据结构课上笔记1

第一节课复习了c语言的一些知识&#xff0c;并简单介绍了数据结构这门课程。 1、引用和函数调用&#xff1a; 1.1引用&#xff1a;对一个数据建立一个“引用”&#xff0c;他的作用是为一个变量起一个别名。这是C对C语言的一个重要补充。 用法很简单&#xff1a; int a 5; …

并查集实现

并查集是什么东西&#xff1f; 它是用来管理元素分组情况的一种数据结构。 他可以高效进行两个操作&#xff1a; 查询a&#xff0c;b是否在同一组合并a和b所在的组 萌新可能不知所云&#xff0c;这个结构到底有什么用&#xff1f; 经分析&#xff0c;并查集效率之高超乎想象…

字符串上的简单动态规划

因为数据结构快学串了&#xff0c;以前又做过一些字符串dp的题&#xff0c;今天突然就想把它们写在一起吧。 直接开始 问题1&#xff1a;给两个字符串&#xff0c;求最长公共子串 问题2&#xff1a;给两个字符串&#xff0c;求最长公共子序列 问题3&#xff1a;给一个字符串…

线段树简单实现

首先&#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…

数据结构课上笔记2

今天继续说明了一些基本概念&#xff0c;讲解了时间空间复杂度。 &#xff08;对于概念的掌握也很重要&#xff09; 元素之间的关系在计算机中有两种表示方法&#xff1a;顺序映像和非顺序映像&#xff0c;由此得到两种不同的储存结构&#xff1a; 顺序存储结构和链式存储结构…

双端单调队列

上次我们介绍了单调栈结构https://blog.csdn.net/hebtu666/article/details/82717317 这次介绍一种新的数据结构&#xff1a;双端队列&#xff1a;双端队列是指允许两端都可以进行入队和出队操作的队列&#xff0c;其元素的逻辑结构仍是线性结构。将队列的两端分别称为前端和后…

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

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

数组实现队列

数组实现队列结构&#xff1a; 相对栈结构要难搞一些&#xff0c;队列的先进先出的&#xff0c;需要一个数组和三个变量&#xff0c;size记录已经进来了多少个元素&#xff0c;不需要其它萌新看不懂的知识。 触底反弹&#xff0c;头尾追逐的感觉。 循环使用数组。 具体解释…

栈/队列 互相模拟实现

用两个栈来实现一个队列&#xff0c;完成队列的Push和Pop操作。 队列中的元素为int类型。 思路&#xff1a;大概这么想&#xff1a;用一个辅助栈把进第一个栈的元素倒一下就好了。 比如进栈1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5 第一个栈&#xff1a; 5 …

数据结构课上笔记3

这节课介绍了线性表结构和顺序表示的一部分内容。 操作太多&#xff0c;而且书上有&#xff0c;就不一一介绍分析了。 线性表定义&#xff1a;n个数据元素的有限序列。 特点&#xff1a; 存在唯一一个称作“第一个”的元素。存在唯一一个称作“最后一个”的元素除最后一个元…

内存分区

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

栈的排序

一个栈中元素的类型为整型&#xff0c;现在想将该栈从顶到底按从大到小的顺序排序&#xff0c;只许申请一个栈。除此之外&#xff0c;可以申请新的变量&#xff0c;但是不能申请额外的数据结构&#xff0c;如何完成排序&#xff1f; 思路&#xff1a; 将要排序的栈记为stack,申…

双链表实现

以前写的不带头的单链表实现&#xff0c;当时也啥也没学&#xff0c;好多东西不知道&#xff0c;加上一心想压缩代码&#xff0c;减少情况&#xff0c;所以写得不太好。 请教了老师&#xff0c;首先是命名问题和代码紧凑性等的改进。还有可读性方面的改进&#xff0c;多写了一…

数据结构作业1 讲解和拓展

原题来自雪梨教育 http://www.edu2act.net/task/list/checked/ 题后给出讲解和扩展 任务1_1 比较下列算法的时间复杂度 任务描述&#xff1a; 下面给出4个算法&#xff0c;请分析下列各算法的时间复杂度&#xff0c;请写清楚题号&#xff0c;并将每个小题的分析过程写出来&…