后序结点和前驱结点
所谓前驱结点和后序结点都是相对中序遍历而言的,即某个结点的前驱结点是指这个在中序遍历中这个结点前面的一个结点;后继结点是中序遍历时这个结点后面的一个结点。因此当考察前驱结点后序结点时,总是使用中序遍历的思路去解决问题。
问题:在一棵二叉树中,每个结点除了val,left,right之外还有一个指向父节点的指针parent,给定任意一个结点node,求出这个结点的后继结点。
思路:方法一:很直接,已知结点具有指向父节点的指针parent,因此对给定的结点node向上循环直到找到根结点root,然后使用中序遍历,每遍历一个结点就与给定的node结点进行比较,当遍历到这个结点node时,那么遍历的下一个结点就是node结点的后序结点了。此方法时间复杂度为O(n),空间复杂度为O(n).
方法二:分情况考虑问题,对于二叉树的问题,很多时候都需要具体情形具体解决,可以画出一棵二叉树,然后对不同情况进行考虑,画图有助于理解和记忆。
情况1:如果node结点有右子树,例如结点④,那么他的后继结点就是它的右子树上最左端的结点,于是在右子树上循环找出最左的结点即可。
情况2:如果node结点没有右子树,并且node结点是父节点的左结点(要求有父节点,如果没有父节点,那么这个结点node就是根结点并且后继结点是null),那么node结点的后继结点就是其父节点。
情况3:如果node结点没有右子树,并且node结点是父节点的右结点,那么需要遍历node结点的父节点,即为s结点,同时得到s结点的父节点p,如果p结点是null,那么说明node没有后继结点,他是最后一个结点,例如结点⑦;否则判断s是否是p的左孩子,如果是左孩子,那么此时的的结点p就是node的后继结点,如果是右孩子那么继续将s向上移动,同时p还是s的父节点,直到移动到p.left==s即s是p的左孩子为止,此时的p就是node的后继结点,否则一直到p==null还不满足要求的话,那么node就没有后继结点,是最后一个结点。
对3种情况逐一处理即可。
这种方法时间复杂度为O(L),空间复杂度为O(1),其中的L是指从node走到后继结点的路径的长度,即在寻找node结点的后继结点时,只需要在从node到后继结点这条路径上面寻找即可,例如⑤的后继结点⑥,在寻找过程中需要遍历的结点是④③⑥;找⑥的后继结点的路径是⑨⑧⑦。并不需要遍历所有的结点。
Tree7:折纸练习题
题目:请把纸条竖着放在桌上,然后从纸条的下边向上对折,压出折痕后再展开。此时有1条折痕,突起的方向指向纸条的背面,这条折痕叫做“下”折痕 ;突起的方向指向纸条正面的折痕叫做“上”折痕。如果每次都从下边向上边对折,对折N次。请从上到下计算出所有折痕的方向。给定折的次数n,请返回从上到下的折痕的数组,若为下折痕则对应元素为"down",若为上折痕则为"up".
思路:对于这种实际问题,动手这一下来找规律:
n=1:下
n=2:下 下 上
n=3:下 下 上 下 下 上 上
n=4:下 下 上 下 下 上 上 下 下 下 上 上 下 上 上
……可以发现是如果建立一棵二叉树,令根结点为下,之后每个结点的左结点为下,右结点为上,那么折痕的顺序恰好就是中序序遍历这棵二叉树的顺序,因此先建立一棵具有上下值的二叉树,然后按照中序遍历的顺序遍历这棵二叉树即可。
首先建立二叉树:显然这是一棵满二叉树,n就是这棵树的层数,按层遍历的方式建立起一棵二叉树,使用一个队列,不需要考虑换行,先将root放入到queue中,然后弹出temp,并为其建立左右结点并分别放入queue中,再弹出一个结点……根据规律,当弹出2^(n-1)-1个结点时恰好将全部结点建立并放入到队列中,于是返回的root结点就是新建二叉树的根结点。
之后使用中序遍历的方式遍历这棵二叉树并将每个结点的值放入到String[]即可。上面的办法当然可行,但是需要建立二叉树比较麻烦,其实由于这道题目比较特殊,每个结点的子节点之后2个值down和up而且左结点必然是down,右结点必然是up,因此其实不需要建立二叉树就可以直接进行遍历,根结点的值直接给定为true或者false,显然要进行的是对一棵二叉树的中序遍历,建立一个递归函数,实现的功能逻辑是:传入根结点的值和根结点所在的层数,要求中序遍历这棵树。对于一个结点root所在的树,要中序遍历这棵树就要先遍历它的左子树,然后遍历这个根结点(将其放入字符串数组string[]),然后再遍历这个结点的右子树。如何遍历左右子树?由于这里没有建立树的结构,于是递推关系是通过层数来建立的,每一次遍历一棵子树时要给定这棵子树的层数level,初始时root结点的level=1;然后递推关系中,它的左子树的层数就是level+1,根结点的值是down,右子树的层数就是level+1,根结点的值就是up,一直递归调用,当level==对折次数n时遍历结束。
这里关键是构造出一个递归的逻辑,递归的逻辑要求完善而严谨,多练多理解。
常识:key1:集合可以使用toArray()方法直接转换为Object[]类型的数组或者使用toArray(T[])转换为指定类型的数组,但是注意,T[]只能是包装类型的数组,即只能是对象类型的数组String[],Integer[],不能是基本类型的数组int[],否则无法转换。本题中是要求转换为String[]数组因此是可以进行转换的。其实这个原理就是数组类型强制转换的原理,Object[]数组可以强转为(Integer[])objects,但是不能转换为int[] ints=(int[])objects;因此遇到集合转换为String[]的问题可以直接使用toArray()进行转换,但是遇到转换为int[]的不能使用toArray(因为这样只能转换为Integer[]),需要遍历集合逐个取出赋值给数组。但是为了统一,还是选择逐个取出集合中的元素赋值给数组中的元素。
importjava.util.*;
//折纸打印问题:利用中序遍历的思想,关键是构造出一个正确的递归函数
publicclass FoldPaper {
public String[] foldPaper(int n) {
//特殊输入
if(n<1) return null;
//由于折痕条数不知道所以使用集合(其实折痕条目是知道的)
List
//调用递归函数getFolder()来返回所有的折痕
this.getFolder(1,n,"down",list);
//key1:将list中的结果逐一取出放入到String[]中,因为list
//但是为了统一,还是使用遍历集合的方式逐个取出
String[] results=newString[list.size()];
for(int i=0;i results[i]=(String)(list.get(i)); } return results; } //设计一个递归方法,用于对给定层次上指定根结点开始的子树进行中序遍历,将遍历的结点放入list中 //level:当前遍历的子树根结点的层数;n:最大层数,即对折次数;val:当前子树根结点的值 private void getFolder(int level,intn,String val,List //边界条件 if(level>n) return; //①先遍历左子树 this.getFolder(level+1,n,"down",list); //②遍历中间结点(这里的所谓遍历是指将其放入到集合list中) list.add(val); //③遍历右子树 this.getFolder(level+1,n,"up",list); }