目录
二叉树
二叉树(Binary Tree)是n(n >= 0 )个节点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根节点和两颗互不相交的、分别称为根节点的左子树和右子树的二叉树组成。
特殊二叉树
有以下三种特殊二叉树:
1.斜树:所有的节点都只有左子树的二叉树叫斜树。所有的节点只有右子树
的二叉树叫右斜树。两者统称为斜树。
2.满二叉树:在一棵二叉树中,如果所有分支节点都存在左子树和右子树,并且所有叶子节点都在同一层上,这样的二叉树称为满二叉树。
3.完全二叉树:对一棵具有n个节点的二叉树按照层序编号,如果编号为i(1 <= i <= n)的节点与同样深度的满二叉树中编号节点为i的节点在二叉树中的位置相同,则这棵二叉树为完全二叉树。
二叉树的性质
二叉树的5个性质:
- 规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(i - 1) (i>0)个结点。
- 规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 2^k - 1 (k>=0)。
- 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1。
- 具有N个结点的完全二叉树的深度k为log(N+1)以2为底向上取整。
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点;
若2i+1<n,左孩子序号:2i+1,否则无左孩子;
若2i+2<n,右孩子序号:2i+2,否则无右孩子。
二叉树的模拟实现
我们用代码模拟实现一个二叉树。
存储结构
在讲解树的时候就讲解了树有两种存储方式,顺序存储和链式存储。
二叉树的顺序存储在下一文讲解堆在介绍。
本文主要讲解链式存储,使用孩子表示法。
接口实现
实现的接口(成员方法)如下:
public class BinaryTree {
// 前序遍历
public void preOrder(TreeNode root) {}
// 中序遍历
void inOrder(TreeNode root) {}
// 后序遍历
void postOrder(TreeNode root) {}
// 获取节点的个数:子问题的思路
int size2(TreeNode root) {}
//获取叶子节点的个数:子问题
int getLeafNodeCount2(TreeNode root) {}
//获取第K层节点的个数
int getKLevelNodeCount(TreeNode root, int k) {}
//获取二叉树的高度 时间复杂度:O(N)
int getHeight(TreeNode root) {}
// 检测值为value的元素是否存在
TreeNode find(TreeNode root, char val) {}
//层序遍历
void levelOrder(TreeNode root) {}
// 判断一棵树是不是完全二叉树
boolean isCompleteTree(TreeNode root) {}
}
内部类
我们使用孩子表示法来存储二叉树,那么在节点内部类中就要包含左孩子和右孩子的引用。
static class TreeNode {
public char val;
public TreeNode left;//左孩子的引用
public TreeNode right;//右孩子的引用
public TreeNode(char val) {
this.val = val;
}
}
遍历
二叉树的遍历(traversing binary tree)是指从根节点出发,按照某种次序依次访问二叉树的所有节点,使得每个节点被访问一次且仅被访问一次。
前序遍历
前序遍历的规则就是:若二叉树为空,则空操作返回,否则先访问根节点,然后前序遍历左子树,再前序遍历右子树。
以打印为例:通俗讲就是遇根节点就打印,然后走左子树,再进右子树。
// 前序遍历
public void preOrder(TreeNode root) {
if (root == null) return;
System.out.print(root.val + " ");
preOrder(root.left);
preOrder(root.right);
}
中序遍历
中序遍历的规则就是:若二叉树为空,则空操作返回,否则从根节点开始(注意不是先访问根节点),中序遍历根节点的左子树,再前序遍历右子树。
以打印为例:通俗讲就是先进左子树,遇根节点如果该根节点的左子树已经打印完了就打印该根节点,再进右子树。
// 中序遍历
void inOrder(TreeNode root) {
if (root == null) return;
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
后序遍历
后序遍历的规则就是:若二叉树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右节点。
以打印为例:通俗讲就是先进左子树,再进右子树,遇根节点如果该根节点的左子树和右子树都已经打印完了就打印该根节点。
// 后序遍历
void postOrder(TreeNode root) {
if (root == null) return;
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val + " ");
}
遍历确定二叉树
根据前中后遍历的特点:
- 前序遍历从前向后的节点是每颗树根节点。
- 中序遍历的特点就是每个节点的左边是左子树的节点,右边是右子树的节点。
- 后序遍历是从后向前的节点是每棵树根节点。
我们只要中序遍历加任一遍历就可以确定当前二叉树。
以前序遍历和中序遍历为例:
根据前序遍历依次往后拿到节点,第一个节点是整棵树的根节点,往后拿到的节点根据中序遍历结果观察是在已确定的二叉树中确定该节点的父亲节点,然后左边就是左孩子,右边就是右孩子。
层序遍历
层序遍历的规则就是:若二叉树为空,则空操作返回,否则从树的第一层,也就是根节点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对节点逐个访问。
代码实现思路:
进行层序遍历时,我们要用到队列(先进先出)这个数据结构,
- 将根结点先放进队列去。
- 每次都记录队列的节点,如果该节点左右孩子不为空就入队。
- 直到队列为空为止。
这样我们入队列是层序遍历的顺序,那出队列也是这个顺序了。
//层序遍历
void levelOrder(TreeNode root) {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
System.out.print(cur.val + " ");
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
获取节点个数
代码思路:
- 如果树为空,返回0。
- 不为空,直接返回左子树和右子树的节点树加上根。
int size2(TreeNode root) {
if (root == null) return 0;
return size2(root.left)
+ size2(root.right) + 1;
}
获取叶子节点的个数
代码思路:
- 如果树为空,返回0。
- 不为空,如果节点左子树和右子树为空则返回1。
- 最后返回根节点左子树和右子树的叶子结点之和。
int getLeafNodeCount2(TreeNode root) {
if (root == null) return 0;
if (root.left == null && root.right == null) {
return 1;
}
return getLeafNodeCount2(root.left) + getLeafNodeCount2(root.right);
}
获取第K层节点的个数
代码思路:
- 如果树为空,返回0。
- 不为空,如果层数为1并且该节点不为空则返回1。
- 最后返回根节点左子树和右子树的第k-1层结点之和。
int getKLevelNodeCount(TreeNode root, int k) {
if (root == null) return 0;
if (k == 1) return 1;
return getKLevelNodeCount(root.left, k - 1) +
getKLevelNodeCount(root.right, k - 1);
}
获取二叉树的高度
代码思路:
- 如果树为空,返回0。
- 不为空,求根节点的左子树的高度和根节点右子树的高度。
- 返回子树高度的最大值然后加上根节点这一层。
int getHeight(TreeNode root) {
if (root == null) return 0;
int leftH = getHeight(root.left);
int rightH = getHeight(root.right);
return leftH > rightH ? leftH + 1 : rightH + 1;
}
检测值为value的元素是否存在
代码思路:
4. 如果树为空,返回空。
5. 不为空,看根节点是不是所求节点,是返回。
6. 不是就在左子树查看,返回值不是空就返回。
7. 左子树结果是空,查看右子树,返回结果。
TreeNode find(TreeNode root, char val) {
if (root == null) return null;
if (root.val == val) return root;
TreeNode ret = find(root.left, val);
if (ret != null) {
return ret;
}
ret = find(root.right, val);
return ret;
}
判断一棵树是不是完全二叉树
我们需要使用队列这个数据结构。
如果是完全二叉树,那么层序遍历结果下节点之间就不会有空值。
代码思路:
- 如果根节点为空直接返回false。
- 不为空将根放入队列。
- 取出队列值,如果值不为空就将左孩子和右孩子入队,为空直接出循环。
- 因为前面是拿到队列中的空节点出循环的,遍历剩余节点如果遇到不为空的节点那该树就不是完全二叉树。
boolean isCompleteTree(TreeNode root) {
if (root == null) return false;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
if (cur != null) {
queue.offer(cur.left);
queue.offer(cur.right);
} else {
break;
}
}
//第2次遍历队列 判断队列当中 是否存在非空的元素
while(!queue.isEmpty()) {
TreeNode cur = queue.peek();
if (cur == null) {
queue.poll();
} else {
return false;
}
}
return true;
}
}