一、二叉树相关概念
1.1 基本术语
- 结点的度:一个结点的子结点的个数称为结点的度。
- 树的度:树中结点的最大度数为树的度
- 树的深度(高度):树中结点的最大层数,从1开始。
1.2 二叉树分类
- 满二叉树:一颗高度为h,并且含有2^h-1个结点的二叉树称为满二叉树。即树中每一层都含有最多的节点。除叶子节点每个节点的度都为2。
- 完全二叉树:当高度为h,具有n个结点的二叉树,与高度为h的满二叉树结点编号完全对应时,即为完全二叉树。(若存在度为1的节点,只能有一个,且只有左孩子无右孩子)
- 二叉排序树:左子树上所有的结点的关键字均小于根节点的关键字,右子树上所有节点的关键字均大于根结点的关键字。(中序遍历可得到递增序列)
- 平衡二叉树:树上任一结点的左子树和右子树深度之差不超过1。
- 哈夫曼树:在含有n个带权叶子节点的二叉树中,其中带权路径长度最小的二叉树为哈夫曼树(最优二叉树)—哈夫曼编码
- 红黑树:红黑树是每个节点都带颜色的树,节点颜色或是红色或是黑色,红黑树是查找树。最重要的性质,从根节点到叶子节点的最长的路径不多于最短路径的长度的2倍。红黑树插入、查找、删除的时间复杂度都为O(logn)
红黑树的五个特性:
(1)每个节点要么是红的,要么是黑的
(2)根节点是黑色的
(3)所有叶子节点即空节点NIL都是黑的
(4)如果一个节点是红色的,则两个子节点都是黑的
(5)对每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点
1.3 二叉树的几条重要性质:
- 非空二叉树上叶子节点的个数等于度数为2的节点数+1
- 结点i所在的层次(深度)为(logn)+1
- N个节点的二叉树的高度为(logn)+1或是log(n+1)
二、二叉树的常见面试题
注意事项:
- 根节点是否为空(递归结点是否为null)
- 是否只存在根节点(一个节点)
- 二叉树通常使用递归,清楚递归结束的条件。例如找路径时是遇到叶子节点停止,大多数是当前结点为null
题目汇总
- 二叉树的遍历:前序遍历、中序遍历、后序遍历、层次遍历(队列)
- 根据中序遍历序列和前序遍历序列(或后序遍历)重建二叉树。
- 求二叉树的所有路径(根到叶子节点)
- 判断二叉树中是否存在一条路径使得节点之和为给定的值。
- 求二叉树的最大深度、最小深度
- 求二叉树第k层的节点个数(层次遍历:队列)
- 求二叉树中叶子节点的个数
- 交换二叉树的左右孩子
- 判断两棵二叉树是否相同(结构和值都相等)
- 判断二叉树是否为完全二叉树
- 判断二叉树是否为平衡二叉树
- 求二叉树的镜像
- 输入两棵二叉树A和B,判断B是否为A的子树
- 求二叉树中节点的最大距离
- 将二叉查找树变为有序的双向链表
- 求二叉树中两个节点的最低公共祖先节点
- 哈夫曼树的构建
详细代码
- 二叉树的遍历:前序遍历、中序遍历、后序遍历、层次遍历(队列)
前序遍历:
//递归:前序遍历
public void preOrder(Node node){
if(node!=null){
System.out.print(node.getPerson().getKey()+"\t");
preOrder(node.getLeftChild());
preOrder(node.getRightChild());
}
}
//非递归:前序遍历
private void preOrder_2(){
Node curNode = rootNode;
while(curNode!=null){
//打印当前节点
System.out.print(curNode.getPerson().getKey()+"\t");
//入栈
stack.push(curNode);
//指向左子节点
curNode = curNode.getLeftChild();
//查找最左边的子节点
while(curNode == null&&!stack.isEmpty()){
curNode = stack.peek();//取栈顶元素
stack.pop();//出栈
curNode = curNode.getRightChild();
}
}
}
中序遍历:
//递归:中序遍历
public void midOrder(Node node){
if(node!=null){
midOrder(node.getLeftChild());
System.out.print(node.getPerson().getKey()+"\t");
midOrder(node.getRightChild());
}
}
//非递归:中序遍历(左根右)
public void midOrder_2(){
Node curNode = rootNode;
while(curNode!=null||!stack.isEmpty()){
if(curNode.getLeftChild()!=null){
stack.push(curNode);
curNode = curNode.getLeftChild();
}else {
//打印最左端的节点
System.out.print(curNode.getPerson().getKey()+"\t");
curNode = curNode.getRightChild();//指向右子节点
while(curNode == null&&!stack.isEmpty()){
curNode = stack.peek();
System.out.print(curNode.getPerson().getKey()+"\t");
stack.pop();
curNode = curNode.getRightChild();
}
}
}
}
后序遍历:
//递归:后序遍历
public void behOrder(Node node){
if (node!=null) {
behOrder(node.getLeftChild());
behOrder(node.getRightChild());
System.out.print(node.getPerson().getKey()+"\t");
}
}
//非递归:后序遍历(左根右)
public void behOrder_2(){
Node curNode = rootNode;
Node preNode = null;
//先将根入栈
stack.push(curNode);
while(!stack.isEmpty()){
curNode = stack.peek();//当前节点设置为栈顶节点
if(curNode.getLeftChild()==null&&curNode.getRightChild()==null
||(preNode!=null&&(curNode.getLeftChild()==preNode||curNode.getRightChild()==preNode))){
//当前节点无左右节点,或者有左节点或右节点,但已经被访问过
//则直接输出该节点,将其出栈,将其设为上一个访问的节点
System.out.print(curNode.getPerson().getKey()+"\t");
stack.pop();
preNode = curNode;//已被访问过
}else {
//如果不满足上面两种情况,则将其右孩子左孩子依次入栈(先右节点再左节点)
if (curNode.getRightChild()!=null) {
stack.push(curNode.getRightChild());
}
if(curNode.getLeftChild()!=null){
stack.push(curNode.getLeftChild());
}
}
}
}