二叉树的子树:
在二叉树中以任何一个节点为头部的整棵树称作二叉树的子树。注意是整棵树!!!!
平衡二叉树(AVL树):
1. 空树是平衡二叉树。
2. 如果一棵树不为空,并且其中所有的子树都满足各自的左子树与右子树的高度差都不超过1.
案例一:给定一棵二叉树的头节点head,判断一棵树是否是平衡二叉树。
解法的整体过程为:二叉树的后序遍历。
对于节点head来说,先遍历head的左子树,遍历的时候需要搜集两个信息:
1. 左子树是否为平衡二叉树(true or false):如果左子树为false,不是平衡二叉树,则无需进行任何的后续过程,因为整棵树已经不是平衡二叉树了,直接返回false
2. 左子树最深可以到达哪一层,LH
如果head的左子树是平衡二叉树,再去遍历head的右子树,遍历过程中依然要搜集两个信息:
1. head右子树是否为平衡二叉树:如果为false,直接返回false。
2. 右子树最深到达哪一层。RH
如果左子树和右子树都是平衡二叉树,则比较LH和RH。如果差值大于1,则返回false,整棵树不是平衡二叉树。
如果差值小于1,则返回LH和RH中较大的一个作为以当前head为头的树的深度。
注意:!!!二叉树中很多面试题都是对二叉树遍历的代码进行了改写!!!
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class CheckBalance {
public:
bool check(TreeNode* root) {
// write code here
//也就是将求高度和求左右子树是否平衡合并到一起了
if(root==NULL)
return true;
int height=0;
return checkbalancedtree(root,height);
}
bool checkbalancedtree(TreeNode* root, int& height){
if(root==NULL){
height=0;
return true;
}
//大致思路就是先判断左右子树是不是平衡树,如果不是的话就直接返回false;如果是的话,根据算出来的左右子树的高度来判断当前root为根的树是不是平衡树,如果
//不是的话,直接返回false;如果是的话,才计算当前root为跟的树的高度同时返回true;
int leftheight,rightheight;
bool left=checkbalancedtree(root->left,leftheight);
bool right=checkbalancedtree(root->right,rightheight);
if(left&&right)//当左右子树都是平衡二叉树时
{
if(leftheight-rightheight>1||leftheight-rightheight<-1)//以root为根的树不是平衡二叉树
return false;
else{
height=leftheight>rightheight?(leftheight+1):(rightheight+1);
return true;
}
}else{
return false;
}
}
};
具体的代码实现如上所示,利用递归进行,这里需要注意的是上面的实现方式是一边计算子树的高度,同时判断子树是不是平衡二叉树;当然也可以将这两个分开进行,也就是将树的高度的计算单独拿出来,虽然也是递归,但是这样的缺点就是可能会重复的遍历树,因为先是计算子树的高度的时候会遍历一遍,然后判断子树是不是平衡二叉树的时候又会遍历一遍,造成额外的时间复杂度!!具体实现可以参考http://www.cnblogs.com/ranjiewen/p/5507621.html;而上面的实现方式的主要思想就是先判断左右子树是不是平衡树,如果不是的话就直接返回false;如果是的话,根据算出来的左右子树的高度来判断当前root为根的树是不是平衡树,如果不是的话,直接返回false;如果是的话,才计算当前root为根的树的高度同时返回true!
搜索二叉树:二叉查找树 或者 二叉排序树
整棵二叉树并且二叉树的所有子树都具有如下的特征:
每棵子树的头节点的值都比各自左子树上的所有节点值要大,也都比各组右子树上的所有节点值要小。
搜索二叉树的性质:搜索二叉树按照中序遍历得到的序列,一定是从小到大排列的。反之,一棵树中序遍历得到的序列是从小到大排列的,说明这棵树一定是搜索二叉树。
红黑树、平衡搜索二叉树(AVL)等,其实都是搜搜二叉树的不同实现。这些实现,都力争做到让搜索二叉树的搜索效率提高并且调整代价尽量小。
案例二:给定一棵二叉树的头节点head,请判断这棵树是否是搜索二叉树。
思路为:
1.改写二叉树的中序遍历
2. 遍历到每个节点的值时,如果一直比上一个遍历的节点值要大,则是搜索二叉树,否则,不是搜索二叉树。
3. 为了方便同时得到当前节点,和上一个遍历的节点,二叉树中序遍历非递归的实现比较合适!!
满二叉树和完全二叉树:
满二叉树:除了最后一层的节点无任何子节点外,剩下每一层上的节点都有两个子节点。
满二叉树的层数记为L,节点数记为N,则N=POW(2,L)-1. L=log2(N+1).
完全二叉树:完全二叉树是指除了最后一层之外,其他每一层的节点数都是满的,最后一层如果也满了,是一棵满二叉树。也是完全二叉树。最后一层如果不满,缺少的节点也全部的集中在右边,那也是一棵完全二叉树。
案例三:给定一棵二叉树的头节点head,判断一棵树是否是完全二叉树。
按照下面的规则来判断会变得比较简单:
1. 采用按层遍历二叉树的方式,从每层的左边向右边依次遍历所有的节点。
2. 如果当前节点有右孩子,但没有左孩子,直接返回false。
3. 如果当前节点并不是左右孩子全有,那之后的节点都必须为叶节点,否则返回false。
4.遍历过程中如果不返回false,遍历结束后返回true即可。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class CheckCompletion {
public:
bool chk(TreeNode* root) {
// write code here
TreeNode* current;
queue<TreeNode*> q;
q.push(root);
bool flag=false;//正常的按层遍历的话只要借助于队列就可以了,不用考虑什么last或者nlast,因为这两个只有需要换行等操作的时候才需要。
while(!q.empty()){
current=q.front();
q.pop();
if(flag==true&&(current->left!=NULL||current->right!=NULL))//说明前面已经有节点它的右孩子为空了,因此这个节点之后的节点的左右孩子都应该为空
return false;
else if(flag==false&¤t->left==NULL&¤t->right!=NULL)
return false;
else if(flag==false&¤t->right==NULL)//说明后面遍历的节点的左右孩子都应该为空
flag=true;
if(current->left!=NULL){
q.push(current->left);
}
if(current->right!=NULL){
q.push(current->right);
}
}
return true;
}
};
具体的实现如上面所示,需要注意的是在按层遍历一棵树的时候,是不需要记last或者nlast的,只要队列就可以了!!!
需要注意的是!!面试中的二叉树节点类型并没有指向父节点的指针,除非特别说明!但是工程上的二叉树节点类型往往多一条指向父节点的指针。
后继节点和前驱节点:
后继节点:一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点。
比如:中序遍历的序列为:DBEAFCG.
G的后继为空!
如果是一个搜索二叉树,那么中序遍历的序列就是升序序列。
前驱节点:
这个节点在中序遍历中的上一个节点。
案例四:
假如二叉树的节点类型中多了一个指向父节点的指针:parent。假设有一棵这种类型的节点组成的二叉树,
树中每个节点的parent指针都正确指向自己的父节点,头节点的parent指向空。只给定在二叉树中的某节点
node,该节点并一定是头节点,可能是树中任何一个节点,请实现返回node的后继节点的函数。
普通方法:
1. 通过node节点的parent指针不断向上找到头节点。
2. 通过找到的头节点,做整棵树的中序遍历,生成中序遍历序列。
3. 在中序遍历序列中,node节点的下一个节点就是其后续节点。
上面这种方式要遍历所有节点,时间复杂度为O(N),额外空间复杂度为O(N)。
最优解法:
如果node节点和node后继节点之间的实际距离为L,最优解法只用走过L个节点,时间复杂度为O(L),额外空间复杂度为O(1).
1. 如果node有右子树,那么后继节点就是右子树上最左边的节点。
2. 如果node没有右子树,那么先看node是不是node父节点的左孩子,如果是左孩子,那么此时node的父节点就是node的后继节点。如果是右孩子,就向上,寻找node的后继
节点。假设向上移动到的节点记为s,s的父节点记为p,如果发现s是p的做孩子,那么节点p就是node节点的后继节点,否则就一直向上移动。
3. 如果一直向上寻找,都移动到空节点了还是没有发现node的后继节点,说明node根本不存在后继节点,返回空即可。