二叉树是每一个节点最多有两个子树的树结构,度多是0,1,2;java
完成二叉树:从左到右依次填满;
满二叉树:除了叶子节点,全部节点都有两个孩子,而且全部叶子节点在同一层;node
1.彻底二叉树除了最后一层外,下一层节点个数是上一层两倍,
若是一颗彻底二叉树的节点总数是n,那么叶子节点个数为n/2(n为偶数)或(n+1)/2(n为奇数);git
写递归算法的关键就是明确函数的定义是什么,而后相信这个定义,利用这个定义推道出最终结果,毫不要跳入递归的细节 (人的小脑壳才能堆几层栈)算法
写树相关的算法,简单的就是说,就是搞清楚当前root节点“该作什么,何时作”这两点,而后根据函数定义递归调用子节点。框架
把题目的要求细化,搞清楚根节点应该作什么,而后剩下的事情抛给前/中/后序的遍历框架就好了,难点在于如何经过题目的要求思考出每个节点须要作什么。函数
递归的时候不要太在乎实现的细节,其本质上是经过栈来实现的,每次在方法里本身调本身,就把新调用的本身压栈,是一个有去有回的过程。
对于递归,关键就是要清楚函数的功能,何时停下来。
对于前序遍历,想先打印根节点,再左再右;那就先输出,再去递归调用,传入当前节点的左子树,左子树一样打印它的根,传入根的左子树,知道整个左子树处理完了,再去处理右子树;post
class BinaryTreeTraverse<T>{ /** * 前序遍历(递归实现); */ public static void preOrderByRecursion(TreeNode node){ if (node == null) return; //递归终止条件; System.out.println(node.value); //获取根节点的值; preOrderByRecursion(node.left); //左子树的根节点; preOrderByRecursion(node.right);//右子树的根节点; } /** * 中序遍历(递归实现) */ public static void inOrderByRecursion(TreeNode node){ if (node == null) return; //递归终止; inOrderByRecursion(node.left); //先左子树; System.out.println(node.value); //再根节点; inOrderByRecursion(node.right); //再右节点; } /** * 后序遍历(递归实现) */ public static void postOrderByRecursion(TreeNode node){ if (node == null) return; postOrderByRecursion(node.left); postOrderByRecursion(node.right); System.out.println(node.value); }
时间复杂度:0(N),每一个节点遍历N次;
空间复杂度:O(N),递归过程当中栈的开销;ui
前序遍历
前序遍历就是咱们来手动实如今递归过程当中的栈。
想要实现先左再右,那压栈的时候右先入栈,左再入栈。
以下图所示;
规则:
压入根节点;
1.弹出就打印;
2.若有右孩子,压入右;
3.若有左孩子,压入左;
重复1.2.3
思考一下这个过程;其实就是至关于两个孩子来替换栈里的根节点,这就很符合前序的定义,根先走,左子树干到了最顶部,要是我左子树还有左孩子,那我也走,两个孩子来替我,每次都是我先走,我左孩子这边整个都完事了,再来我右孩子,由于我右孩子被压在最下面。this
public static void preOrder(TreeNode node){ Stack<TreeNode> stack = new Stack<>(); if (node != null){ stack.push(node); //根节点入栈; } while (!stack.isEmpty()){ TreeNode top = stack.pop(); //弹出就打印; System.out.println(top.value); if (top.right != null) stack.push(top.right); //依次入栈右节点和左节点; if (top.left != null) stack.push(top.left); } }
中序遍历
规则:
1.整条左边界依次入栈;
2.条件1执行不了了,弹出就打印;
3.来到弹出节点的右子树上,继续执行条件1;(右树为空,执行2.弹出就打印;右树有节点,执行1.压栈;
说明:将整个树全用左边界去看,都是先处理了左边界,将左边界分解成了左头,头先入,左再入,而后弄不动了,弹出,而后看其右节点,再把右节点里的左依次进去;就这样往返;
以下如所示;.net
public static void inOrder(TreeNode node){ Stack<TreeNode> stack = new Stack<>(); while (!stack.isEmpty() || node != null){ if (node != null){ //条件1;能往左走就往左走; stack.push(node); node = node.left; }else { TreeNode top = stack.pop(); //条件2;弹出打印; System.out.println(top.value); node = top.right; //条件3;来弹出节点右树上,继续1; } } }
后序遍历
后序遍历能够用前序遍从来解决,想一下前序遍历:根左右,咱们先压右树再压左树。怎么实现根右左呢,能够先压左树再压右树嘛,而后反过来不就是左右根了吗?(反过来用栈来实现,栈一个很大的做用就是实现逆序)
public static void postOrder(TreeNode node){ Stack<TreeNode> stackA = new Stack<>(); Stack<TreeNode> stackB = new Stack<>(); if (node != null){ stackA.push(node); } while (!stackA.isEmpty()){ TreeNode top = stackA.pop(); stackB.push(top); //栈A弹出的进入栈B;先实现根右左,B倒序实现左右根; if (top.left != null) stackA.push(top.left); if (top.right != null) stackA.push(top.right); } while (!stackB.isEmpty()){ System.out.println(stackB.pop().value); } }
二叉树的结构中只有父节点指向孩子节点,孩子节点不能向上指,因此须要栈。
而Morris遍历的实质就是让下层节点也能指向上层,怎么办呢,一个节点有两个指针,左和右,若是这两个指针都有指向具体节点了那指定用不上了。
可是二叉树但是有不少空闲指针啊,好比说全部的叶子节点,它们的指针就都指向null,因此能够利用其right指针指向上层。
这样把下层往上层创建链接之后,cur指针就能够完整的顺着一个链条遍历完整个树。
由于不用堆栈,因此其空间复杂度变为O(1);
以下图所示:
cur指针走的顺序:1 2 4 2 5 1 3 6 3 7;
核心: 以某个根节点开始,找到其左子树的最右节点(必然是个叶子节点),而后利用其right指针指向根节点(创建从下到上的链接)
原则:
(迭代法的中序遍历咱们能够将整个树所有分红左边界去看,如上面的图,其实在morris遍历里咱们能够将整个树所有分红右边界来看)
public static void morris(TreeNode node){ if(node == null){ return; } TreeNode cur = node; TreeNode mostRightNode = null; //记录cur左子树的最右节点; while(cur != null){ mostRightNode = cur.left; if(mostRightNode != null){ //cur有左子树,就证实有下一层; //找到cur左子树的最右节点(找到cur下一层右边界的最后一个) while(mostRight.right != null && mostRightNode != cur){ //1.最右节点为空说明到头了,找到了; //2.最右节点指向上层说明已经处理过了,来过了; mostRightNode = mostRightNode.right; } //走到这里证实跳出上面循环,无非两个缘由: //1.右边没了;2.右边指向上层了(以前就处理过了); if(mostRightNode.right == null){ mostRightNode.right = cur; //创建从最右节点到cur的链接; cur = cur.left; //处理下一个节点; continue; }else{ //能到这里说明已经创建了最右节点到cur的链接; //也说明cur指的这个节点是第二次到了,断开链接; mostRightNode.right = null; } } //cur右移的状况: //1.cur没有左子树了(天然要开始处理右子树) //2.cur有左子树,可是cur左子树最右节点已经指向cur了(执行完上面else断开后,cur左边已经彻底处理好了,开始右移。) cur = cur.right; } }
前序遍历
1.对于cur只达到一次的节点(没有左子树),cur达到就打印;
2.对于cur到达两次的节点(有左子树),到达第一次时打印;
public static void preOrderMorris(TreeNode node){ if (node == null){ return; } TreeNode cur = node; TreeNode mostRightNode = null; while (cur != null){ mostRightNode = cur.left; if (mostRightNode != null){ //到达两次的节点; //找到cur左子树的最右节点; while (mostRightNode.right != null && mostRightNode.right != cur){ mostRightNode = mostRightNode.right; } if (mostRightNode.right == null){ mostRightNode.right = cur; //指向上层cur; System.out.println(cur.value); //第一次到的时候打印; cur = cur.left; continue; }else{ mostRightNode.right = null; //第二次到时不打印; } }else { System.out.println(cur.value); //只到达一次的节点; } cur = cur.right; } }
中序遍历
1.对于cur只达到一次的节点(没有左子树),cur达到就打印;
2.对于cur到达两次的节点(有左子树),到达第二次时打印;
public static void inOrderMorris(TreeNode node){ if (node == null){ return; } TreeNode cur = node; TreeNode mostRightNode = null; while (cur != null){ mostRightNode = cur.left; if (mostRightNode != null){ //有左子树,到达两次的节点; while (mostRightNode.right != null && mostRightNode.right != cur){ mostRightNode = mostRightNode.right; } if (mostRightNode == null){ mostRightNode.right = cur; //第一次不打印; cur = cur.left; continue; }else { System.out.println(cur.value); //第二次遇到时打印; mostRightNode.right = null; } }else { System.out.println(cur.value); //只到达一次的节点,遇到就打印; } cur = cur.right; } }
后序遍历
后序遍历比前面两个要复杂一点;
将一个节点的连续右节点当成是一个单链表看,以下图所示:
当咱们到达最左侧,也就是左边连线已经建立完毕了。
打印 4
打印 5 2
打印 6
打印 7 3 1
咱们将一个节点的连续右节点当成一个单链表来看待。
当咱们返回上层以后,也就是将连线断开的时候,打印下层的单链表。
好比返回到 2,此时打印 4
好比返回到 1,此时打印 5 2
好比返回到 3,此时打印 6
最后别忘记头节点那一串,即1 3 7
那么咱们只须要将这个单链表逆序打印就好了。
这里不该该打印当前层,而是下一层,不然根结点会先与右边打印。
public static void postOrderMorris(TreeNode node){ if (node == null){ return; } TreeNode cur = node; TreeNode mostRightNode = null; while (node != null){ mostRightNode = cur.left; if (mostRightNode != null){ while (mostRightNode.right != null && mostRightNode.right != cur){ mostRightNode = mostRightNode.right; } if (mostRightNode.right == null){ mostRightNode.right = cur; cur = cur.left; continue; }else { //能到这里的都是达到过两次的,也就是是有左孩子的。 mostRightNode.right = null; //这时候是已经返回上层以后,断开了链接,因此打印下层的单链表; postMorrisPrint(cur.left); } } cur = cur.right; } postMorrisPrint(node); //最后把头节点那一串右打印一遍; } public static void postMorrisPrint(TreeNode node){ TreeNode reverseList = postMorrisReverseList(node); //反转链表; TreeNode cur = reverseList; while (cur != null){ System.out.println(cur.value); cur = cur.right; } postMorrisReverseList(reverseList); //最后再还原; } public static TreeNode postMorrisReverseList(TreeNode node){ TreeNode cur = node; TreeNode pre = null; while (cur != null){ TreeNode next = cur.right; cur.right = pre; pre = cur; cur = next; } return pre; }
层次遍历顾名思义就是一层一层的遍历。从上到下,从左到右,那须要借助什么结构呢?
能够采用队列的结构,利用其先进先出的特性,每一层依次入队,再依次出队。
对该层节点进行出队时,将这个节点的左右节点入队,这样当一层全部节点出队完成后,下一层也入队完成了。
/** * 层次遍历 * 借助队列的结构,每一层依次入队,再依次出队; * 对该层节点进行出队操做时,须要将该节点的左孩子和右孩子入队; */ public static int layerOrder(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); queue.add(node); while (!queue.isEmpty()){ int size = queue.size(); //当前层的节点数量; for (int i = 0; i < size; i++){ TreeNode front = queue.poll(); System.out.println(front.value); if (front.left != null) queue.add(front.left); if (front.right != null) queue.add(front.right); } } }
二叉树的最大深度是根节点到最远叶子结点的距离;
递归实现
1.终止条件:在二叉树为空的时候,深度为1;
2.缩小范围,等价关系:给定一个二叉树,其深度为左子树的深度和右子树的深度的最大值+1;
/** * 求最大深度(递归) * 最大深度是左子树和右子树的最大深度的大的那个+1; */ public static int maxDepthByRecursion(TreeNode node){ if(node == null) return 0; int leftDepth = maxDepthByRecursion(node.left); int rightDepth = maxDepthByRecursion(node.right); return Math.max(leftDepth,rightDepth)+1; }
非递归实现(层次遍历)
关键点:每遍历一层,则计数器加+1;直到遍历完成,获得树的深度。
采用二叉树的层次遍历,来计数总共有多少层,采用队列的结构,当前层节点出队,计数器加1,而后把下一层的节点所有入队,直到队为空。
/** * 求最大深度(非递归) * 层次遍历(BFS) * 每遍历一层,则计数器加+1;直到遍历完成,获得树的深度。 */ public static int maxDepth(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; //层数; queue.add(node); while (!queue.isEmpty()){ level++; int levelNum = queue.size(); //每层的节点数; for (int i = 0; i < levelNum; i++){ TreeNode front = queue.poll(); //当前层出队,下一层入队; if (front.left != null) queue.add(front.left); if (front.right != null) queue.add(front.right); } } return level; }
二叉树的深度是根节点到最近叶子节点的距离;
递归实现
此题不能像最大深度那样直接求两颗子树的最大而后+1,最大深度能够是由于取大值不会影响一棵树为空的时候。可是取最小就不同了,若是一棵树为空,那最小的应该是不为空的那边的值,可是还按原来方式就变成了0+1;好比下面这个例子:最小深度应该我2.可是按原来方式写的话最小深度就会变为1.因此,在处理每个节点的时候,若是有两个孩子,那就能够继续取小+1,若是只有一个孩子,那就只能去递归它的孩子。
/** * 求最小深度(递归) * 注意和求最大深度的区别; */ public static int minDepthByRecursion(TreeNode node){ if (node == null) return 0; if (node.right == null && node.left == null) return 1; if (node.left == null && node.right != null) return minDepthByRecursion(node.right) + 1; if (node.right == null && node.left != null) return minDepthByRecursion(node.left) + 1; return Math.min(minDepthByRecursion(node.left), minDepthByRecursion(node.right))+1; }
非递归实现(层次遍历)
关键点:每遍历一层,则计数器加+1;在遍历的过程当中,若是出现了没有叶子节点的节点,那就能够结束了,就是最小深度。
采用二叉树的层次遍历,来计数总共有多少层,采用队列的结构,当前层节点出队,计数器加1,而后把下一层的节点所有入队,直到遇到叶子节点或队为空。
/** * 求最小深度(非递归) */ public static int minDepth(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; queue.add(node); while (!queue.isEmpty()){ level++; int levelnum = queue.size(); for(int i = 0; i < levelnum; i++){ TreeNode front = queue.poll(); if (front.left == null && front.right == null){ return level; //遇到第一个无叶子节点的时候,该节点的深度为最小深度; } if (front.right != null){ queue.add(front.right); } if (front.left != null){ queue.add(front.left); } } } return level; }
根据二叉树的前序或后序中的一个再加上中序来还原出整个二叉树。
注意: 中序是必须有的,由于其能够明确的把左右子树分开。
先看下3种遍历的特色(以下图):
特色
前序的遍历顺序是根左右,中序的遍历顺序是左中右,
递归实现
1.前序的第一个节点是root节点,对应可以找到在中序中的位置。
2.根据中序遍历的特色,在找到的根前边序列是左子树的中序遍历序,后边序列是右子树的中序遍历。
3.求出左边序列的个数,好比设为leftSize,那在前序序列中紧跟着根的leftSize个元素是左子树的前序序列,后边的为右子树的前序序列。
4.这样就又得到了两个子树的前序遍历和中序遍历,开始递归。
/** * 根据前序遍历和中序遍历构造二叉树; */ public static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder){ if (preorder == null){ return null; } //由于咱们要在中序遍历中寻找某个元素的位置,而后划分左右子树 //用一个map来存储元素在中序遍历中的位置, Map<Integer,Integer> map = new HashMap<>(); for(int i = 0; i < inorder.length; i++){ map.put(inorder[i], i); } return buildTreeByPreOrder(preorder, inorder, 0, preorder.length-1, 0, inorder.length-1,map); } //传入前序和中序,传入前序的左右边界,中序的左右边界; private static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder, int preleft, int preright, int inleft, int inright, Map<Integer,Integer> map){ if (preleft > preright) return null; //得到整颗树的根节点:先序中的第一个元素; TreeNode root = new TreeNode(preorder[preleft]); //获得此元素在中序中的位置,以此进行划分出左右子树; int rootIndex = map.get(root); //获得左子树的大小;(注意此时不能直接是rootIndex,inleft不老是从0开始的,想一下创建右子树的左子树。 int leftTreeSize = rootIndex - inleft; //左子树的中序:inleft不变,inright为rootIndex-1; //左子树的前序:preleft为根后一位,即preleft+1,preright为根后leftTreeSize位,即preleft+leftTreeSize; root.left = buildTreeByPreOrder(preorder,inorder,preleft+1, preleft+leftTreeSize, inleft, rootIndex-1, map); //右子树的中序:inleft为rootIndex+1,inright不变; //右子树的前序:preleft为左子树的右边界+1,即preleft+leftTreeSize+1,preright不变; root.right = buildTreeByPreOrder(preorder,inorder,preleft+leftTreeSize+1, preright, rootIndex+1, inright,map); return root; }
后序的遍历顺序是左右根,中序的遍历顺序是左根右,
递归实现
1.后序的第一个节点是root节点,对应可以找到在中序中的位置。
2.根据中序遍历的特色,在找到的根前边序列是左子树的中序遍历,后边序列是右子树的中序遍历。
3.求出左边序列的个数,好比设为leftSize,那在后序序列中的leftSize个元素是左子树的后序序列,后边的为右子树的后序序列。
4.这样就又得到了两个子树的后序遍历和中序遍历,开始递归。
/** * 根据后序遍历和中序遍历构造二叉树 */ public static TreeNode buildTreeByPostOrder(int[] postorder, int[] inorder){ if (postorder == null){ return null; } //由于咱们要在中序遍历中寻找某个元素的位置,而后划分左右子树 //用一个map来存储元素在中序遍历中的位置, Map<Integer,Integer> map = new HashMap<>(); for(int i = 0; i < inorder.length; i++){ map.put(inorder[i], i); } return buildTreeByPostOrder(inorder, postorder, 0, inorder.length-1, 0, postorder.length-1,map); } //传入后序和中序,传入后序的左右边界,中序的左右边界; private static TreeNode buildTreeByPostOrder(int[] inorder, int[] postorder, int inleft, int inright, int postleft, int postright, Map<Integer,Integer> map){ if (postleft > postright) return null; //得到整颗树的根节点:后序中的最后个元素; TreeNode root = new TreeNode(postorder[postleft]); //获得此元素在中序中的位置,以此进行划分出左右子树; int rootIndex = map.get(root.value); //获得左子树的大小;(注意此时不能直接是rootIndex,inleft不老是从0开始的,想一下创建右子树的左子树。 int leftTreeSize = rootIndex - inleft; root.left = buildTreeByPostOrder(inorder, postorder, inleft, rootIndex-1,postleft,postleft+leftTreeSize-1,map); root.right = buildTreeByPostOrder(inorder,postorder,rootIndex+1, inright, postleft+leftTreeSize,postright-1,map); return root; }
package xin.utils; import jdk.internal.dynalink.beans.StaticClass; import sun.reflect.generics.tree.VoidDescriptor; import javax.sound.midi.Soundbank; import java.awt.font.TransformAttribute; import java.util.*; public class Tree { } /** * 定义一个二叉树节点; */ class TreeNode<T>{ public T value; //数据; public TreeNode<T> left; //左子树; public TreeNode<T> right;//右子树; public TreeNode(){} //空参的; public TreeNode(T value){ //有参的; this.value = value; } public TreeNode(T value, TreeNode left, TreeNode right){ this.value = value; this.left = left; this.right = right; } } class BinaryTreeTraverse<T> { /** * 前序遍历(递归实现); */ public static void preOrderByRecursion(TreeNode node) { if (node == null) return; //递归终止条件; System.out.println(node.value); //获取根节点的值; preOrderByRecursion(node.left); //左子树的根节点; preOrderByRecursion(node.right);//右子树的根节点; } /** * 前序遍历(非递归实现); * 本质上就是维持递归实现的栈; * 1.先入栈根节点,输出根节点的值,再入栈其右节点,左节点;(为了出栈的时候先出左节点,再出右节点); * 2.出栈左节点,输出值,再入栈左节点的右节点、左节点;直到遍历完左子树; * 3.出栈右节点,输出值,再入栈右节点的右节点、左节点;直到遍历完右子树; * 每次都是出栈一个根节点,若是有孩子,就依次入栈其右节点和左节点。 * 规则: * 压入根节点; * 1.弹出就打印; * 2.若有右孩子,压入右; * 3.若有左孩子,压入左;重复; * (至关于两个孩子替换掉了栈里的根节点,这就很符号:根先走了,左子树干到了最顶部,要是我左子树还有孩子,ok,我也走,两个孩子来替我, * 要是左子树没孩子了,我本身出去,我这里就完事了;再去看右子树就能够了) */ public static void preOrder(TreeNode node) { Stack<TreeNode> stack = new Stack<>(); if (node != null) { stack.push(node); //根节点入栈; } while (!stack.isEmpty()) { TreeNode top = stack.pop(); //弹出就打印; System.out.println(top.value); if (top.right != null) stack.push(top.right); //依次入栈右节点和左节点; if (top.left != null) stack.push(top.left); } } /** * 中序遍历(递归实现) */ public static void inOrderByRecursion(TreeNode node) { if (node == null) return; //递归终止; inOrderByRecursion(node.left); //先左子树; System.out.println(node.value); //再根节点; inOrderByRecursion(node.right); //再右节点; } /** * 中序遍历(非递归实现) * 规则: * 1.整条左边界依次压栈; * 2.条件1执行不了,弹出就打印; * 3.来到弹出节点右树上,继续执行条件1;(右树为空,执行2.弹出打印;右树不为空,执行1;压栈) * 说明:将整个树全用左边界去看,都是先处理了左边界,将左边界分解成了左头,头先入,左再入,而后弄不动了,弹出,而后看其右节点,再把右节点里的左依次进去;就这样往返; */ public static void inOrder(TreeNode node) { Stack<TreeNode> stack = new Stack<>(); while (!stack.isEmpty() || node != null) { if (node != null) { //条件1;能往左走就往左走; stack.push(node); node = node.left; } else { TreeNode top = stack.pop(); //条件2;弹出打印; System.out.println(top.value); node = top.right; //条件3;来弹出节点右树上,继续1; } } } /** * 后序遍历(递归实现) */ public static void postOrderByRecursion(TreeNode node) { if (node == null) return; postOrderByRecursion(node.left); postOrderByRecursion(node.right); System.out.println(node.value); } /** * 后序遍历(非递归实现) * 想一下前序遍历:根左右;过程是先压右孩子,再压左孩子; * 若是咱们想实现根右左:那就把前序里的换成先压左,再压右;就处理成了 根右左; * 而后再从后往前看,就变成了右左根;因此能够再准备一个栈,用来把第一个栈弹出的压到第二个,那第二个弹出的时候就倒过来了; * 要记住:栈有实现倒序的功能; */ public static void postOrder(TreeNode node) { Stack<TreeNode> stackA = new Stack<>(); Stack<TreeNode> stackB = new Stack<>(); if (node != null) { stackA.push(node); } while (!stackA.isEmpty()) { TreeNode top = stackA.pop(); stackB.push(top); //栈A弹出的进入栈B;先实现根右左,B倒序实现左右根; if (top.left != null) stackA.push(top.left); if (top.right != null) stackA.push(top.right); } while (!stackB.isEmpty()) { System.out.println(stackB.pop().value); } } /** * Morris遍历; * 二叉树的结构中只有父节点指向孩子节点,孩子节点不能向上指,因此须要栈。 * 而morris遍历的实质就是让下层节点可以指向上层。怎么办呢,一个节点有两个指针,左和右,若是这两个指针上都有指向具体的节点确定就不行了。 * 可是二叉树上有不少空闲指针,好比全部的叶子节点,它们的指针就指向null,因此能够利用其right指针指向上层的节点。 * 这样链接后,cur这个指针就能够完整的顺着一个链条遍历完整个树。 * 核心:以某个根节点开始,找到它左子树的最右侧节点(必然是个叶子节点),而后利用其right指向根节点(完成向上层的返回)。 * 到达两次的是有左子树的节点; * 到达一次的是没有左子树的节点; */ public static void morris(TreeNode node) { if (node == null) { return; } TreeNode cur = node; TreeNode mostRight = null; //cur左子树的最右节点; while (cur != null) { mostRight = cur.left; if (mostRight != null) { //cur有左子树; //找到左子树的最右节点; while (mostRight.right != null && mostRight.right != cur) { mostRight = mostRight.right; //1.右边为空了证实到头了(找到了);2.右边指向上层了证实以前就处理过了,结束; } //走到这里证实跳出上面循环,无非两个缘由: //1.右边没了;2.右边指向上层了(以前就处理过了); if (mostRight.right == null) { //右边走到头了; mostRight.right = cur; //左子树的最右节点指向上层(cur); cur = cur.left; //cur左移,处理下一个节点; continue; //这次循环结束,开始下一个cur; } else { //证实这个mostRight的right指针已经处理过了,即mostRight已经指向了cur; // 能到这里说明咱们已经回到了根节点,而且重复了以前的操做;也说明咱们已经彻底处理完了此根节点左边的的树了,把路断开; mostRight.right = null; } } //cur右移的状况: //1.cur没有左子树了(天然要开始处理右子树) //2.cur有左子树,可是cur左子树最右节点已经指向cur了(执行完上面else断开后,cur左边已经彻底处理好了,开始右移。) cur = cur.right; } } /** * 前序遍历(Morris实现); * 1.对于cur只到达一次的节点(没有左子树),cur到达就打印; * 2.对于cur到达两次的节点,到达第一次时打印; */ public static void preOrderMorris(TreeNode node) { if (node == null) { return; } TreeNode cur = node; TreeNode mostRightNode = null; while (cur != null) { mostRightNode = cur.left; if (mostRightNode != null) { //到达两次的节点; //找到cur左子树的最右节点; while (mostRightNode.right != null && mostRightNode.right != cur) { mostRightNode = mostRightNode.right; } if (mostRightNode.right == null) { mostRightNode.right = cur; //指向上层cur; System.out.println(cur.value); //第一次到的时候打印; cur = cur.left; continue; } else { mostRightNode.right = null; //第二次到时不打印; } } else { System.out.println(cur.value); //只到达一次的节点; } cur = cur.right; } } /** * 中序遍历(Morris实现); * 1.对于cur只到达一次的节点(没有左子树),cur到达就打印; * 2.对于cur到达两次的节点,到达第二次时打印; */ public static void inOrderMorris(TreeNode node) { if (node == null) { return; } TreeNode cur = node; TreeNode mostRightNode = null; while (cur != null) { mostRightNode = cur.left; if (mostRightNode != null) { //有左子树,到达两次的节点; while (mostRightNode.right != null && mostRightNode.right != cur) { mostRightNode = mostRightNode.right; } if (mostRightNode == null) { mostRightNode.right = cur; //第一次不打印; cur = cur.left; continue; } else { System.out.println(cur.value); //第二次遇到时打印; mostRightNode.right = null; } } else { System.out.println(cur.value); //只到达一次的节点,遇到就打印; } cur = cur.right; } } /** * 后序遍历(morris实现) * 后序遍历比前面两个要复杂一点; * 将一个节点的连续右节点当作是一个单链表来看待, * 当返回上层后,也就是将创建的连线断开后,打印下层的单链表; * 单链表逆序打印,就和咱们作的把单链表逆序同样。 */ public static void postOrderMorris(TreeNode node) { if (node == null) { return; } TreeNode cur = node; TreeNode mostRightNode = null; while (node != null) { mostRightNode = cur.left; if (mostRightNode != null) { while (mostRightNode.right != null && mostRightNode.right != cur) { mostRightNode = mostRightNode.right; } if (mostRightNode.right == null) { mostRightNode.right = cur; cur = cur.left; continue; } else { //能到这里的都是达到过两次的,也就是是有左孩子的。 mostRightNode.right = null; //这时候是已经返回上层以后,断开了链接,因此打印下层的单链表; postMorrisPrint(cur.left); } } cur = cur.right; } postMorrisPrint(node); //最后把头节点那一串右打印一遍; } public static void postMorrisPrint(TreeNode node) { TreeNode reverseList = postMorrisReverseList(node); //反转链表; TreeNode cur = reverseList; while (cur != null) { System.out.println(cur.value); cur = cur.right; } postMorrisReverseList(reverseList); //最后再还原; } public static TreeNode postMorrisReverseList(TreeNode node) { TreeNode cur = node; TreeNode pre = null; while (cur != null) { TreeNode next = cur.right; cur.right = pre; pre = cur; cur = next; } return pre; } /** * 层次遍历 * 借助队列的结构,每一层依次入队,再依次出队; * 对该层节点进行出队操做时,须要将该节点的左孩子和右孩子入队; */ public static void layerOrder(TreeNode node) { if (node == null) return; Queue<TreeNode> queue = new LinkedList<>(); queue.add(node); while (!queue.isEmpty()) { int size = queue.size(); //当前层的节点数量; for (int i = 0; i < size; i++) { TreeNode front = queue.poll(); System.out.println(front.value); if (front.left != null) queue.add(front.left); if (front.right != null) queue.add(front.right); } } } } class Depth{ /** * 求最大深度(递归) * 最大深度是左子树和右子树的最大深度的大的那个+1; */ public static int maxDepthByRecursion(TreeNode node){ if(node == null) return 0; int leftDepth = maxDepthByRecursion(node.left); int rightDepth = maxDepthByRecursion(node.right); return Math.max(leftDepth,rightDepth)+1; } /** * 求最大深度(非递归) * 层次遍历(BFS) * 每遍历一层,则计数器加+1;直到遍历完成,获得树的深度。 */ public static int maxDepth(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; //层数; queue.add(node); while (!queue.isEmpty()){ level++; int levelNum = queue.size(); //每层的节点数; for (int i = 0; i < levelNum; i++){ TreeNode front = queue.poll(); //当前层出队,下一层入队; if (front.left != null) queue.add(front.left); if (front.right != null) queue.add(front.right); } } return level; } /** * 求最小深度(递归) * 注意和求最大深度的区别; */ public static int minDepthByRecursion(TreeNode node){ if (node == null) return 0; if (node.right == null && node.left == null) return 1; if (node.left == null && node.right != null) return minDepthByRecursion(node.right) + 1; if (node.right == null && node.left != null) return minDepthByRecursion(node.left) + 1; return Math.min(minDepthByRecursion(node.left), minDepthByRecursion(node.right))+1; } /** * 求最小深度(非递归) */ public static int minDepth(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; queue.add(node); while (!queue.isEmpty()){ level++; int levelnum = queue.size(); for(int i = 0; i < levelnum; i++){ TreeNode front = queue.poll(); if (front.left == null && front.right == null){ return level; //遇到第一个无叶子节点的时候,该节点的深度为最小深度; } if (front.right != null){ queue.add(front.right); } if (front.left != null){ queue.add(front.left); } } } return level; } } class BuildBinaryTree{ /** * 根据前序遍历和中序遍历构造二叉树; */ public static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder){ if (preorder == null){ return null; } //由于咱们要在中序遍历中寻找某个元素的位置,而后划分左右子树 //用一个map来存储元素在中序遍历中的位置, Map<Integer,Integer> map = new HashMap<>(); for(int i = 0; i < inorder.length; i++){ map.put(inorder[i], i); } return buildTreeByPreOrder(preorder, inorder, 0, preorder.length-1, 0, inorder.length-1,map); } //传入前序和中序,传入前序的左右边界,中序的左右边界; private static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder, int preleft, int preright, int inleft, int inright, Map<Integer,Integer> map){ if (preleft > preright) return null; //得到整颗树的根节点:先序中的第一个元素; TreeNode root = new TreeNode(preorder[preleft]); //获得此元素在中序中的位置,以此进行划分出左右子树; int rootIndex = map.get(root.value); //获得左子树的大小;(注意此时不能直接是rootIndex,inleft不老是从0开始的,想一下创建右子树的左子树。 int leftTreeSize = rootIndex - inleft; //左子树的中序:inleft不变,inright为rootIndex-1; //左子树的前序:preleft为根后一位,即preleft+1,preright为根后leftTreeSize位,即preleft+leftTreeSize; root.left = buildTreeByPreOrder(preorder,inorder,preleft+1, preleft+leftTreeSize, inleft, rootIndex-1, map); //右子树的中序:inleft为rootIndex+1,inright不变; //右子树的前序:preleft为左子树的右边界+1,即preleft+leftTreeSize+1,preright不变; root.right = buildTreeByPreOrder(preorder,inorder,preleft+leftTreeSize+1, preright, rootIndex+1, inright,map); return root; } /** * 根据后序遍历和中序遍历构造二叉树 */ public static TreeNode buildTreeByPostOrder(int[] postorder, int[] inorder){ if (postorder == null){ return null; } //由于咱们要在中序遍历中寻找某个元素的位置,而后划分左右子树 //用一个map来存储元素在中序遍历中的位置, Map<Integer,Integer> map = new HashMap<>(); for(int i = 0; i < inorder.length; i++){ map.put(inorder[i], i); } return buildTreeByPostOrder(inorder, postorder, 0, inorder.length-1, 0, postorder.length-1,map); } //传入后序和中序,传入后序的左右边界,中序的左右边界; private static TreeNode buildTreeByPostOrder(int[] inorder, int[] postorder, int inleft, int inright, int postleft, int postright, Map<Integer,Integer> map){ if (postleft > postright) return null; //得到整颗树的根节点:后序中的最后个元素; TreeNode root = new TreeNode(postorder[postleft]); //获得此元素在中序中的位置,以此进行划分出左右子树; int rootIndex = map.get(root.value); //获得左子树的大小;(注意此时不能直接是rootIndex,inleft不老是从0开始的,想一下创建右子树的左子树。 int leftTreeSize = rootIndex - inleft; root.left = buildTreeByPostOrder(inorder, postorder, inleft, rootIndex-1,postleft,postleft+leftTreeSize-1,map); root.right = buildTreeByPostOrder(inorder,postorder,rootIndex+1, inright, postleft+leftTreeSize,postright-1,map); return root; } }