一、二叉树的概念
1. 1 定义
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:
- 每个结点最多有两棵子树,即二叉树不存在度大于 2 的结点。
- 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树。
如下图为一颗二叉树:1. 2 概念
-
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6;
-
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
-
叶子节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
-
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
-
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
-
根结点:一棵树中,没有双亲结点的结点;如上图:A 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
-
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。
1. 3 两种特殊的二叉树
-
满二叉树:每层节点的个数都达到最大值,即如果一棵二叉树的层数为K,且节点总数是2^k -1,则它就是满二叉树。
-
完全二叉树:相比较于满二叉树而言,完全二叉树少一个“右下角”。
二、 二叉树的存储
- 二叉树的存储结构分为:顺序存储和类似于链表的链式存储。
- 二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:
// 孩子表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
// 孩子双亲表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
Node parent; // 当前节点的根节点
}
三、 二叉树的遍历
给定一颗如下的二叉树:
代码定义构建这颗二叉树:
- 节点的定义
private static class TreeNode{
char val; //根节点值
TreeNode left; //左子树根节点
TreeNode right; //右子树根节点
public TreeNode(char val) { //构造方法
this.val = val;
}
}
- 代码创建如上图所示的二叉树:
/**
* 创建一颗二叉树,返回根节点值
* @return
*/
public static TreeNode build(){
TreeNode nodeA =new TreeNode('A');
TreeNode nodeB =new TreeNode('B');
TreeNode nodeC =new TreeNode('C');
TreeNode nodeD =new TreeNode('D');
TreeNode nodeE =new TreeNode('E');
TreeNode nodeF =new TreeNode('F');
TreeNode nodeG =new TreeNode('G');
TreeNode nodeH =new TreeNode('H');
nodeA.left=nodeB;
nodeA.right=nodeC;
nodeB.left=nodeD;
nodeB.right=nodeE;
nodeC.left=nodeF;
nodeC.right=nodeG;
nodeE.right=nodeH;
return nodeA;
}
(1)前序遍历:根->左->右
/**
* 传入一颗一root为根节点的二叉树,返回其先序遍历的结果
* @param root
*/
public static void preOrder(TreeNode root){
//边界条件
if (root == null){
return;
}
System.out.print(root.val+" "); //打印根节点
//按照先序遍历的方式递归访问左树
preOrder(root.left);
//按照先序遍历的方式递归访问右树
preOrder(root.right);
}
- 测试:
public static void main(String[] args) {
TreeNode root=build();
System.out.println("先序遍历的结果:");
preOrder(root);
}
// 先序遍历的结果:D B E H A F C G
(2)中序遍历:左->根->右
/**
* 中序遍历:左-->根-->右
* 传入一颗一root为根节点的二叉树,返回其中序遍历的结果
* @param root
*/
public static void inOrder(TreeNode root){
if (root == null){
return;
}
//先递归访问左子树
inOrder(root.left);
System.out.print(root.val+" "); //根节点
//再递归访问右子树
inOrder(root.right);
}
// 中序遍历的结果:D B E H A F C G
(3)后序遍历:左->右->根
/**
* 后序遍历:左-->右-->根
* 传入一颗一root为根节点的二叉树,返回其后序遍历的结果
* @param root
*/
public static void postOrder(TreeNode root){
if (root == null){
return;
}
//先递归访问左子树
postOrder(root.left);
//再递归访问右子树
postOrder(root.right);
System.out.print(root.val+" "); //根节点
}
// 后序遍历的结果:D H E B F G C A
(4)层序遍历
/**
* 二叉树的层序遍历
* 传入一颗二叉树的根节点,返回其层序遍历的结果
* @param root
*/
public static void levelOrder(TreeNode root){
//边界条件
if (root == null){
return ;
}
//借助队列实现遍历过程
Deque<TreeNode> deque =new LinkedList<>();
deque.offer(root);
while (! deque.isEmpty()){
//循环遍历,取出队列中元素加入到list集合中
int size =deque.size();
for (int i = 0; i < size; i++) {
TreeNode cur =deque.poll();
System.out.print(cur.val+" ");
//处理左树
if (cur.left != null){
deque.offer(cur.left);
}
//处理右树
if (cur.right != null){
deque.offer(cur.right);
}
}
}
}
// 层序遍历的结果是:A B C D E F G H
-层序遍历相关练习
leetcode102 二叉树的层序遍历
题解:
public class Main{
/* 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。
(即逐层地,从左到右访问所有节点)。
* @param root
* @return
*/
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ret = new ArrayList<>();
//边界条件
if (root == null){
return ret;
}
//借助队列实现遍历过程
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root); //将根节点入队
while (! deque.isEmpty() ){
//使用tem数组保存当前层的所有元素
List<Integer> curList =new ArrayList<>();
//取出当前层所有的元素添加到curList中
int size = deque.size();
for (int i=0;i<size;i++){
TreeNode cur =deque.poll(); //出队
curList.add(cur.val);
//判断当前层左右子树的是否为空
if ( cur.left != null){
deque.offer(cur.left); //入队
}
if(cur.right != null){
deque.offer(cur.right);
}
}
//循环走完,当前层的所有元素都处理完,且下一层的元素都被保存到队列中去了
ret.add(curList);
}
return ret;
}
}
-层序遍历统计节点个数
/**
* 层序遍历的方式获取二叉树的节点个数
* @param root
* @return
*/
public static int getNodesNonRecursion(TreeNode root) {
if (root == null){
return 0;
}
int size = 0; //统计节点个数
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root); //先将根节点入队
while (!queue.isEmpty()){
//队列不为空时,将队列元素出队,节点个数++
TreeNode cur = queue.poll();
size++;
if (cur.left != null){
queue.offer(cur.left);
}
if (cur.right != null){
queue.offer(cur.right);
}
}
return size;
}
四、 二叉树的其他方法
- 获取二叉树的节点个数
/**
* 传一一颗二叉树的根节点,返回该二叉树的节点个数
* @param root
* @return
*/
public static int getNodes(TreeNode root){
//边界条件
if (root == null){
return 0;
}
return 1+getNodes(root.left)+getNodes(root.right);
}
- 获取二叉树的叶子节点个数
/**
* 传一一颗二叉树的根节点,返回该二叉树的叶子节点个数
* @param root
* @return
*/
public static int getLeafNodes(TreeNode root){
//边界条件
if ( root == null){
return 0;
}
if (root.left==null && root.right== null){
//此时,只有根节点
return 1;
}
//当前树不为空且存在子树
return getLeafNodes(root.left)+getLeafNodes(root.right);
}
- 判断二叉树中是否包含指定值的元素
/**
* 传入一颗以root为根节点的值,判断其中是否包含值为val的值
* @param root
* @param val
* @return
*/
public static boolean contains(TreeNode root,char val){
if (root == null){
return false;
}
if (root.val==val){
return true;
}
//此时,根节点的值和val不相等
//遍历子树中是否包含val
return contains(root.left,val) || contains(root.right,val);
}
- 求二叉树的高度
/**
* 传入一颗二叉树的根节点,返回二叉树的高度
* @param root
* @return
*/
public static int height(TreeNode root){
if (root == null){
return 0;
}
//此时,二叉树不为空,根节点就是 1层 继续求出子树的高度
int leftHeight =height(root.left);
int rightHeight =height(root.right);
int max=Math.max(leftHeight,rightHeight);
return 1+max;
}
- 求二叉树第K层节点个数
/**
* 获取以root为根节点的二叉树第K层的节点个数
* @param root
* @param k 表示层数
* @return k曾节点个数
*/
public static int getKLevelNodes(TreeNode root,int k){
//边界条件
if ( root == null && k<=0){
return 0;
}
if (k ==1){
return 1;
}
//此时二叉树不为空且 k>=2
//将二叉树问题拆分为左右子树的子问题:以root为根节点的第K层 == 以root.left为根的 k-1层 +root.right为根的k-1层
return getKLevelNodes(root.left,k-1) +getKLevelNodes(root.right,k-1);
}
相关面试题:
1.相同的树:LeetCode链接
题解:
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
//两棵树都为空
if(p ==null && q==null){
return true;
}
//其中有一棵树为空
if(p==null || q==null){
return false;
}
//根节点的值不相等
if(p.val != q.val){
return false;
}
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
}
2.另一颗子树:LeetCode链接
题解:
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null && subRoot ==null){
//两树都为空
return true;
}
if (root ==null || subRoot ==null){
//一棵树为空
return false;
}
//此时,两颗树都不为空
//判断subRoot和root是否是同一颗树 左树中包含 右树中包含
return isSameTree(root,subRoot) || isSubtree(root.left,subRoot) ||isSubtree(root.right,subRoot);
}
public boolean isSameTree(TreeNode p, TreeNode q) {
//边界条件
if (p ==null && q==null){
//两棵树都为空
return true;
}
if (p==null || q==null){
//一颗数为空
return false;
}
if (p.val != q.val){
return false;
}
//递归判断左右子树的情况
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
}
3.判断是否是平衡二叉树:LeetCode链接
题解:
class Solution {
public boolean isBalanced(TreeNode root) {
if(root ==null){
return true;
}
//计算树的高度
int left=height(root.left);
int right=height(root.right);
int abs=Math.abs(left-right);
if(abs>1){
return false;
}
return isBalanced(root.left) && isBalanced(root.right);
}
//计算树高的方法
public static int height(TreeNode root){
if(root==null){
return 0;
}
return 1+Math.max(height(root.left),height(root.right));
}
}
4.判断是否是对称二叉树:LeetCode链接
题解:
class Solution {
public boolean isSymmetric(TreeNode root) {
//边界
if(root == null){
return true;
}
//判断两棵树是否是镜像
return isMirror(root.left,root.right);
}
public boolean isMirror(TreeNode left,TreeNode right){
//两颗树都为空
if(left ==null && right==null){
return true;
}
//一棵树为空
if(left==null || right==null){
return false;
}
//判断两棵树的节点值是否相等
if(left.val != right.val){
return false;
}
//判断 left的左树要和right的右树相同 && left的右树要和right的左树相同
return isMirror(left.left,right.right) && isMirror(left.right,right.left);
}
}
The end----