今天我们来学习树树是一个很神奇的结构,用图来表示就像一棵真正的树一样。
上图就是一个树结构,我们对于树的定义:其子树不能够交叉。
树的本质实际上就是用递归实现的,所以我们的大多数与树相关的题目就是用递归的思想来实现,具体看后面博客。
树有很多概念,但是大多数都很好理解,接下来请看树中经常用到的概念:
节点的度:一个节点含有的子树个数
树的度:一个树中,所以节点的度的最大值就成为树的度
叶子节点/终端节点:叶子节点就是度为0的节点
孩子节点/子节点:例如上图,其中②节点和③节点就是①节点的孩子节点
根节点:树的第一层的节点,也是没有双亲的节点,就如上图中的①节点
节点的层次:该节点位置位于树的第几层,便是树的层次
树的高度:从根开始到最多层次,最底下的节点,其长度就成为树的高度
树的深度:树的深度是相对的,要看我们选取从哪个节点开始算高度
二叉树是树的一种,也是我们平常做题中最常用的一种树的类型,其每一个节点的度都<=2。
二叉树的包含很广泛,例如空树也可以被成为二叉树,以及只有根的树也能称为二叉树。
二叉树又有两种特殊类型(不完全):满二叉树和完全二叉树
满二叉树是除了最下面一层的节点外,其他节点的度都为2。
满二叉树有一个规律:节点个数 = 2 ^ (层数-1)
完全二叉树的定义是完全二叉树的每一个节点序号都可以与满二叉树的序号相对。我们用图直接表示一下完全二叉树与非完全二叉树的区别:
满二叉树是一种特殊的完全二叉树。
二叉树的其中一条规律是:假设树中有N个节点,则该树的边是N-1条边。
二叉树的规律大多数都可以自己推算,在这里博主写出一个比较有趣的规律:在一个树中,假设叶子节点的个数设为n0,有两个节点的个数为n2,则有右边公式:n0 = n2 + 1;
以下是该公式的推算过程:
当我们熟悉这个规律后,就可以利用其规律,做出很多选择题,我们拿下面一个选择题来举例:
因为题目中显示的是2n个节点,也就是有偶数个节点,我们可以通过画图,画出该拥有偶数节点的完全二叉树:
通过观察该拥有偶数节点的二叉树我们可以发现,度为一的节点只有一个,所以我们可以列出下面推算结果:
所以最后答案选则A。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
二叉树的存储有两种方式:顺序存储(也就是堆结构)和类似于链表的链式存储。在这里我们先介绍链式存储
在链式存储中,二叉树的结构分为三部分:left(左树)、val(本身数值)、right(右树)。
我们的二叉树就经常由该形式构成:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
要了解一个数据结构,我们可以从仿写的方向入手,下面是博主自己仿写的一棵二叉树:
public class BinaryTree {
/**
* 创建树的节点
*/
public static class TreeNode{
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val){
this.val = val;
}
}
public TreeNode root;
/**
* 先自己创建一个树
*/
public void creatTree(){
TreeNode node1 = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6);
node1.left = node2;
node1.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
root = node1;
}
public static void main(String[] args) {
BinaryTree binaryTree = new BinaryTree();
binaryTree.creatTree();
}
}
创建后的树大致如下图:
二叉树有四种遍历方式,分别为:前序遍历、中序遍历、后序遍历、层序遍历。
在这里,我们先实现前、中、后序的遍历功能,代码如下:
/**
* 前序遍历
* @param root
*/
public void preOrdder(TreeNode root){
if(root==null) return;
System.out.print(root.val + " ");
preOrdder(root.left);
preOrdder(root.right);
}
/**
* 中序遍历
* @param root
*/
public void inOrder(TreeNode root){
if(root==null) return;
preOrdder(root.left);
System.out.print(root.val + " ");
preOrdder(root.right);
}
/**
* 后序遍历
* @param root
*/
public void postOrder(TreeNode root){
if(root==null) return;
preOrdder(root.left);
preOrdder(root.right);
System.out.print(root.val + " ");
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
二叉树有很多基本操作,如下:
// 获取树中节点的个数
int size(Node root);
// 获取叶子节点的个数
int getLeafNodeCount(Node root);
// 子问题思路-求叶子结点个数
// 获取第K层节点的个数
int getKLevelNodeCount(Node root);
// 获取二叉树的高度
int getHeight(Node root);
// 检测值为value的元素是否存在
Node find(Node root, int val);
//层序遍历
void levelOrder(Node root);
// 判断一棵树是不是完全二叉树
boolean isCompleteTree(Node root);
接下来让我们逐个逐个完成。
✨size函数
该函数的完成思路十分清晰,在完成前、中、后序遍历后,我们只需要选择一种遍历方式,然后在每一次遍历的时候+1就可以记好。然后还有另一种思路,是利用递归,每进入一次该函数,就证明有一个节点,所以我们在最后返回的时候都要+1。用两种思路来完成,一种是遍历思路,另一种是子问题思路。具体代码如下:
//遍历思路
public int size;
int size(TreeNode root){
if(root==null) return 0;
size++;
size(root.left);
size(root.right);
return size;
}
//子问题思路
int size2(TreeNode root){
if(root==null) return 0;
return size2(root.left) + size2(root.right) + 1;
}
✨getLeafNodeCount(叶子节点个数)函数
该函数也可以分为两个思路:遍历思路以及子问题思路。分别来分析:
遍历思路是我们遍历每一个节点,当遇到节点的左树和右树同时为空的时候,则让我们记录的变量+1,其他时候不管;
子问题思路是我们当碰到节点走到null的时候,就返回0,如果走到了左树和右树同时为空的时候,我们就返回1。其他时候不管。
// 获取叶子节点的个数
public int leafNum;
int getLeafNodeCount(TreeNode root){
if(root==null) return 0;
if(root.left==null&&root.right==null){
leafNum++;
}
getLeafNodeCount(root.left);
getLeafNodeCount(root.right);
return leafNum;
}
// 子问题思路-求叶子结点个数 //子问题就是把单独的一个拿出来看
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);
}
✨getKLevelNodeCount(第k层节点个数)函数
获取第k层节点的个数,对于第一层的节点来说,只需要往下走k-1层,就可以走到第k层;对于第二层来说,需要往下走k-2层;以此类推...直到我们走到第k层——也就是当k为1的时候,我们就走到了第k层了。具体代码如下:
// 获取第K层节点的个数
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);
}
✨getHeight(获取二叉树高度)函数
获取二叉树的高度,在这里我们的思路是使用递归,也就是子问题思路,要计算某节点的高度,就是求该节点的下面树的高度,树的高度又要看该节点的左子树右子树中较高的树,计算出较高的树后就加上其节点本身+1。将每个节点的左子树和右子树比较高度,将较大数比较出来后再加上自己本身的这一层,就是二叉树的高度了。
我们可以画图来演示一下:
我们要用代码来实现,就需要分别记录左树的高度与右树高度,之后再对比,具体代码如下:
// 获取二叉树的高度
int getHeight(TreeNode root){
if(root==null) return 0;
int leftNum = getHeight(root.left); //记录左树高度
int rightNum = getHeight(root.right); //记录右树高度
return leftNum>rightNum ? leftNum + 1 : rightNum + 1; //比较左树右树的中较高的树后,我们可以就返回较大者+1,即加上其本身的高度
}
在写完上面代码后,我们就可以解决一道LeetCode题了二叉树的最大深度,其思路和此函数是一样的。
✨find(检测value元素是否存在)函数
这个只需要使用遍历思路,前、中、后序遍历都可以使用,对比找到后返回即可,具体代码如下:
// 检测值为value的元素是否存在
TreeNode find(TreeNode root, int val){
if(root==null) return null;
if(root.val==val) return root;
TreeNode left = find(root.left,val);
if(left==null){
return null;
}else if(left.val==val){
return left;
}
TreeNode right = find(root.right,val);
if(right==null){
return null;
}else if(right.val==val){
return right;
}
return null;
}
✨levelOrder(层序遍历)函数
层序遍历的完成需要我们加入多一个结构来完成:队列
队列的作用是存储每一层的数据,我们只需要先记录前一层节点的个数,弹出对应层数的元素时存入每个节点的左右节点。具体操作图如下:
具体代码如下:
//层序遍历
void levelOrder(TreeNode root){
//写一个Queue,每次弹出queue.size个
if(root==null) return;
Queue queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
while(size>0){
TreeNode str = queue.poll();
System.out.print(str.val + " ");
if(str.left!=null) queue.offer(str.left);
if(str.right!=null) queue.offer(str.right);
size--;
}
}
}
在写完该函数之后,我们就可以挑战写一写LeetCode题——二叉树的层序遍历✨,LeetCode中的这道题与该函数还是有些不同的,小伙伴们也可以看博主对这道题的解析(广告时间)
✨isCompleteTree(判断完全二叉树)函数
判断是否为完全二叉树,我们可以参考上面函数的思想,使用一个队列,但是不用记录每层的大小,我们只需要拉出一个节点之后(用cur接收),将其左右节点都放入队列,无论是不是有null出现。直至我们用cur接收了之后为null就退出循环。然后判断该队列中有没有其他节点出现,还是全部都是null,如果全是null,那么则为完全二叉树;但是如果是除了null外队列中还有节点,那么就不是完全二叉树。具体代码如下:
// 判断一棵树是不是完全二叉树
boolean isCompleteTree(TreeNode root){
if(root==null) return true;
Queue queue = new LinkedList<>();
queue.offer(root); //先放入根节点,为了能够进入循环
TreeNode cur = root; //先创建一下cur
while(cur!=null){
cur = queue.poll(); //将队列的元素取出来,用cur记录
if(cur!=null){ //若刚刚提取出来的元素不为null,将左、右节点放入队列
queue.offer(cur.left);
queue.offer(cur.right);
}
}
//此时弹出来则证明遇到了null,那么此时就要判断队列中有没有节点,一直弹出元素
//直至栈为空或者遇到了节点
while(!queue.isEmpty()){
TreeNode str = queue.poll();
if(str!=null){ //如果遇到了节点,不为空,则证明该树不为完全二叉树,返回false
return false;
}
}
return true; //如果都挺到了这里,那么就需要返回true
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
以上就大致是树的全部内容了要巩固知识与熟练使用,我们需要多刷LeetCode题目,下面是博主写的题集,里面包含着博主写的一些认为较有价值的题的解析,里面也包含着树的题目,感兴趣的小伙伴可以点进去看一下,博主也会持续更新~
以上!便是全部的啦
又是收获满满的一天~