题目:有一棵二叉树,请设计一个算法判断它是否是完全二叉树。给定二叉树的根结点root,请返回一个bool值代表它是否为完全二叉树。树的结点个数小于等于500。
思路:判断一棵树是否是完全二叉树,显然按照完全二叉树的定义应该使用按层遍历的方式来进行。按层遍历使用while循环并借助队列来进行。逐个元素遍历:每弹出一个结点temp要将其左右结点放入到队列中,逻辑:如果temp既有左孩子又有右孩子,那么继续向下进行;如果temp只有有孩子没有左孩子那么显然不是完全二叉树,返回false,结束方法;如果temp只有左孩子没有右孩子或者没有任何孩子,那么制定一个标记flag=1,之后如果某个结点有任何孩子都不是完全二叉树;当遍历完全部的结点后如果依然没有返回那么就是完全二叉树,返回true。
注意:在面试中,通常二叉树中任何结点都只有一个值val和left、right子节点,没有指向父节点的指针,但在工程上通常有指向父节点的指针。
即判断是否是完全二叉树其实很简单,只有四种情况,分别做判断即可:
情况1:temp的left!=null&&right!=null,那么不做处理,继续遍历
情况2:temp的left==null&&right!=null,那么显然不是完全二叉树,return false;
情况3:temp的left!=null&&right==null,那么对之后遍历的结点需要特殊考察,如果之后的结点还有子节点就不是完全二叉树,返回false,这里使用一个标记遍历flag来作为循环时是否允许子节点为空的依据。
情况4:temp的left==null&&right==null,那么同上,对之后遍历的结点要特殊考察。只是情况3中要将结点left放入队列中,情况4中不需要将结点放入队列中。
import java.util.*;
//判断是否是完全二叉树:按层遍历,借助队列来实现
public class CheckCompletion {
public boolean chk(TreeNode root) {
//①创建一个队列
LinkedList<TreeNode> queue=new LinkedList<>();
//②将根结点放入队列
TreeNode temp=root;
queue.add(temp);
//③循环:弹出--压入左右子节点
boolean flag=true;
while(queue.size()!=0){
temp=queue.poll();
TreeNode left=temp.left;
TreeNode right=temp.right;
//如果flag==false说明之前的结点只有左结点或者没有子节点,因此之后的结点都不能有子节点
if((!flag)&&(left!=null||right!=null)) return false;
if(left!=null&&right!=null){
//左右结点都存在
queue.add(left);
queue.add(right);
}else if(left==null&&right!=null){
//右结点存在,左结点不存在,一定为false
return false;
}else if(left!=null&&right==null){
//左结点存在右结点不存在则接着遍历,只是之后的结点都不能有孩子,作一个标记记录
flag=false;
//当然存在的子节点还是要放入到队列中
queue.add(left);
}else{
//左右结点都为空,则不需要放入队列,只要做标记,之后的结点都不能有子节点
flag=false;
}
}
//当队列为空时,说明遍历完成,此时还没有返回说明是完全二叉树
return true;
}
}
后序结点和前驱结点
所谓前驱结点和后序结点都是相对中序遍历而言的,即某个结点的前驱结点是指这个在中序遍历中这个结点前面的一个结点;后继结点是中序遍历时这个结点后面的一个结点。因此当考察前驱结点后序结点时,总是使用中序遍历的思路去解决问题。
问题:在一棵二叉树中,每个结点除了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到后继结点这条路径上面寻找即可,例如⑤的后继结点⑥,在寻找过程中需要遍历的结点是④③⑥;找⑥的后继结点的路径是⑨⑧⑦。并不需要遍历所有的结点。