不知不觉断更挺久了,这篇讲二叉树
二叉树在初级数据结构中的重要性相信不需要过多解释,它既是重点也是难点。二叉树的操作基本都是用递归来实现的,中间会结合题目来展示二叉树的各种操作,所以本篇会比较长,请保持耐心看完,相信你会有收获
目录
1.树形结构
在了解二叉树之前我们需要知道简单了解一下树的定义以及一些概念
1.1树的定义
树是一种非线性的数据结构,是由多个节点组成的有层次关系的集合,看起来就像一颗倒挂的树,如下图:
树中有一个节点没有前驱节点,这个节点称为根节点
除根节点外,其余的节点又可以看成多个互不相交的集合,每一个集合就是一颗子树
1.2树的概念
以上图作为例子来介绍一下树里面的概念
- 节点的度:一个结点含有子树的个数称为该结点的度,比如E节点的度为2
- 树的度:节点的度的最大值
- 叶子节点(终端节点):度为0的节点,比如上面的节点P、Q
- 父节点(双亲节点):一个节点的前驱节点称为此节点的父节点,如G是N的父节点
- 子节点(孩子节点):一个节点的后继节点称为此节点的子节点,如N是G的子节点
- 根节点:树中没有父节点的节点
- 节点层次:从根节点开始往下,根节点为1层,根节点的子节点为第二次,以此类推
- 树的高度:节点的最大层数
- 树的深度:不同的书中的定义不同,这里就取其中的一个定义:根节点到指定节点的最长路径的节点数,比如I,从A到I的节点数为3,所以I的深度就为3,
2.二叉树
2.1二叉树概念
有了上面的基础,我们就可以给二叉树一个定义了:
二叉树是树中的一类特殊的树,其特殊在树中所有节点的度都小于等于2,而且二叉树的子树有左右之分,而且次序不能颠倒
在二叉树中又有两个特殊的二叉树
满二叉树:二叉树中每一层的节点数都达到最大值,依据等比数列求和公式计算,k层的二叉树的总的节点数量就是(2^k)-1
完全二叉树:假设二叉树有n个节点,按照从上往下,从左往右的顺序进行编号(假设从0开始编号),直至n-1的时候中间的编号不断
从图像上来说,满二叉树和完全二叉树的图如下:
2.2二叉树的性质
- 规定根节点的层数为1,那么第k层最多有2^(k-1)个节点
若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^(k)-1
对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的结点个数为 n2,则有n0=n2+1
推导:n个节点共有n-1条边(一个节点对应一条边,根节点没有边)度为0、1、2的节点分别用n0、n1、n2表示,那么有 n1+2*n2+1=n0+n1+n2,化简之后就是n0=n2+1
具有n个结点的完全二叉树的深度k为 log2(n+1)向上取整
对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,下标为i的节点
其父节点下标为(i-1)/2(向下取整)
子节点的下标为2*i+1(左孩子)2*i+2(右孩子)
3.二叉树的操作
到这里就是本篇的重点了,二叉树的操作基本上都是通过递归来完成的,代码写起来可能就几行,但要理解的话却并不容易,为理解方便,我们先不构建二叉树,后面的操作以下图的二叉树为例
3.1二叉树遍历
3.1.1递归实现遍历
二叉树的遍历分前序遍历、中序遍历、后序遍历和层序遍历
所谓的前序、中序和后序指的是打印根节点的次序(根节点在最前面、中间、最后打印)
层序遍历则是从根节点开始从上往下,从左往右进行遍历
前序遍历:按照根节点->左子树->右子树的顺序进行遍历,结果为1→2→4→5→3→6→7
中序遍历:按照左子树->根节点->右子树的顺序进行遍历,结果为4→2→5→1→6→3→7
后序遍历:按照左子树->右子树->根节点的顺序进行遍历,结果为4→5→2→6→7→3→1
以前序遍历为例,代码如下:
public void preOrder(TreeNode root) {
if(root==null) {
return;
}
System.out.println(root.val);
preOrder(root.left);
preOrder(root.right);
}
中序和后序同理,只不过代码的顺序需要变化一下
//中序遍历
public void inOrder(TreeNode root) {
if(root==null) {
return;
}
inOrder(root.left);
System.out.println(root.val);
inOrder(root.right);
}
//后序遍历
public void postOrder(TreeNode root) {
if(root==null) {
return;
}
inOrder(root.left);
inOrder(root.right);
System.out.println(root.val);
}
在力扣上面有关于二叉树遍历的题目,不同的是返回值是List,题目链接:力扣
主要的代码就是上面的代码,因为返回值是List,所以建立顺序表,将遍历的值接收即可
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ret=new ArrayList<>();
if(root==null) {
return ret;
}
ret.add(root.val);
List<Integer> left=preorderTraversal(root.left);
ret.addAll(left);
List<Integer> right=preorderTraversal(root.right);
ret.addAll(right);
return ret;
}
}
3.1.2非递归实现遍历
上面的遍历是通过递归完成的,下面采用非递归的方式来完成,先看前序遍历
非递归的方式需要借助栈来完成,而且根节点不能移动,所以新建一个节点cur
按照前序遍历的顺序,cur依次经过1、2、4,在经过这些节点的时候依次将其存放在栈中,同时依次打印