Java|二叉树基础详解
文章目录
- 树结构的引入
- 关于树的基础概念
-
- 树的特点
- 树的概念
- 二叉树
-
- 二叉树常考的性质
- 常见二叉树
- 关于完全二叉树的编号
- 二分搜索树(BST)
- 平衡二叉树
- 二叉树的实现
- 二叉树的基本操作
- 总结
树结构的引入
树:高效的查找与搜索语义
在OS中的文件系统就是基于树结构进行文件管理的
如果所有文件都放在一个“目录”下,若当前操作系统有1亿个文件,最坏情况遍历1亿次才能找到特定元素
OS分为多级目录,我们只需要O(logn)的时间复杂度就可以找到指定元素
关于树的基础概念
我们已经知道,线性数据结构或者说线性表的元素之间是逻辑上连续的,呈直线排列
而树是一种非线性的数据结构,它是有n个有限结点组成一个具有层次关系的集合
树的特点
树有一个特殊的结点,称为根结点,根结点没有前驱结点
除根结点,其余结点被分成m个集合,彼此之间互不相交,若相交,就不是树
每棵子树有且只有一个前驱,但可以有0或者多个后继
树的概念
1.子树不相交
2.除了根结点没有父结点之外,每个结点有且只有一个父结点
3 树,边的个数x以及结点的个数n之间的关系:x = n - 1 (每个结点都有一个与父结点相连的边,只有根结点没有)
4.结点和树的度:
结点的度:该结点中包含的子树个数称为该结点的度
树的度:书中最大的结点的度称为了树的度
5.叶子结点:度为0的结点
非叶子结点:度不为0的结点,说明还存在子树
6.根结点:没有父结点的结点,树中有且只有一个根结点
7.结点的层次:从根结点开始计算,根结点为第一层
树的高度:当前树中结点层次最大的即为树的高度
二叉树
树中结点最大的度为2的树称之为二叉树
二叉树中,一个结点最多有两颗子树,二叉树结点的度小于等于2
且子树有左右之分,左右的顺序不能颠倒,分为只有左子树没有右子树的结点,只有右子树没有左子树的结点
二叉树常考的性质
1.在深度为k的二叉树中,最多存在2^k - 1个结点
2.在第k层,该层最多有2^(k -1)个结点
3.由于任意二叉树都满足结点个数n和边长具备x = n - 1
在二叉树中,度为2的结点和度为0的结点有以下关系:n0 = n2 + 1
度为0度结点个数永远比度为2的结点的个数多一个
关于第三种性质的推导:
二叉树中只有三种结点:n0, n1, n2
n0 + n1 + n2 = n ( 三种结点的个数相加就是总结点的个数 )
n1 + 2n2 = n - 1 ( n1有一条边,n2有两条边,相加后总共有 n - 1条边 )
将两个式子化简可以得到:n0 = n2 + 1
常见二叉树
满二叉树:在该二叉树中,每一层的结点个数都是最大值
完全二叉树:完全二叉树的结点编号和满二叉树完全一致,一一对应
满二叉树就是一个特殊的完全二叉树
在完全二叉树中不存在只有右子树而没有左子树的结点,换言之,如果存在度为1的结点,这个结点必然只有左树而没有右树的结点,这样的结点有且只有一个
关于完全二叉树的编号
1.若根结点从1开始编号,若任意一个结点x,即存在子树也有父结点,则该结点的父结点编号为x / 2,其左子树编号为2x,右子树编号为2x + 1
2.若根结点从0开始编号(“堆”就是基于数组实现的完全二叉树):则在完全二叉树中,若任意结点既有子树也有父结点,该结点编号为x,则父结点的编号为(x - 1) / 2,左子树的结点编号为2x + 1,右子树的结点编号为2x + 2
二分搜索树(BST)
结点的值之间有一个大小关系:左子树结点值 < 根结点值 < 右子树结点值
若在BST中查找一个元素,其实就是二分查找
平衡二叉树
该树中任意一个结点的左右子树高度差 <= 1
注意是所有结点都要满足,仅仅是根结点的子树满足情况是不够的,因为子树的子树可能不平衡
二叉树的实现
树结点:
class TreeNode<E> { E val; TreeNode<E> left; TreeNode<E> right; public TreeNode(E val){ this.val = val; }}
二叉树的纯手工构建:
public void build(){ TreeNode<Character> node = new TreeNode<>('A'); TreeNode<Character> node1 = new TreeNode<>('B'); TreeNode<Character> node2 = new TreeNode<>('C'); TreeNode<Character> node3 = new TreeNode<>('D'); TreeNode<Character> node4 = new TreeNode<>('E'); TreeNode<Character> node5 = new TreeNode<>('F'); TreeNode<Character> node6 = new TreeNode<>('G'); TreeNode<Character> node7 = new TreeNode<>('H'); node.left = node1; node.right = node2; node1.left = node3; node1.right = node4; node4.left = node6; node6.right = node7; node2.right = node5; root = node;}
构建完成后的二叉树见后图
二叉树的基本操作
所有二叉树的基础操作,或者说所有二叉树的问题的解决思路都是遍历问题的衍生
遍历
遍历是指按照一定的顺序访问这个集合的所有元素,做到不重复不遗漏
对于二叉树来说一共有四种遍历方式:前序遍历、中序遍历、后序遍历、层序遍历
前三种称为深度优先遍历(DFS),最后一种称为广度优先遍历(BFS)
深度优先遍历借助栈实现,广度优先遍历借助队列实现
二叉树图例:
前序遍历
先访问根结点,然后递归访问左子树,再递归问右子树(根左右)
上图为例的遍历结果:A B D E G H C F
代码实现:
/** * 传入根结点,就能按照前序遍历的方式输出 - 根左右 * @param root */public void preOrder(TreeNode root){ if(root == null){ return; } System.out.print(root.val + " "); preOrder(root.left); preOrder(root.right);}
中序遍历
先递归的访问左子树,然后访问根结点,最后访问右子树(左根右)
当根结点第二次走到时才能访问
上图为例的遍历结果:D B G H E A C F
代码实现:
/** * 传入根结点,就能按照中序遍历的方式输出 - 左根右 * @param root */public void inOrder(TreeNode root){ if(root == null){ return; } inOrder(root.left); System.out.print(root.val + " "); inOrder(root.right);}
后序遍历
先递归访问左子树,在递归访问右子树,最后访问根结点
上图为例的遍历结果:D H G E B F C A
代码实现:
/** * 传入根结点,就能根据后序遍历的方式输出 - 左右根 * @param root */public void postOrder(TreeNode root){ if(root == null){ return; } postOrder(root.left); postOrder(root.right); System.out.print(root.val + " ");}
层序遍历
将二叉树一层一层遍历,从上往下,从左至右
前三种遍历通过画栈的方式来辅助遍历顺序
上图为例的遍历结果:A B C D E F G H
层序遍历需要借助递归函数和队列来实现,因此在之后的博客中会专门讲解前中后序以及层序遍历
前中后序遍历的结果
- 前序遍历的第一个结点一定是根结点
- 中序遍历左子树在根结点的左侧,右子树的遍历结果在根结点的右侧
- 后序遍历的结果集反转之后是(根右左),后序遍历的最后一个结果一定为根结点
二叉树基本题
求结点个数
public int getNodes(TreeNode root){ if(root == null){ return 0; } //如果根结点不为空,那么返回自身加上左子树的结点个数和右子树的结点个数之和 return 1 + getNodes(root.left) + getNodes(root.right); //可以简化为一句话 //return root == null ? 0 : 1 + getNodes(root.left) + getNodes(root.right);}
求叶子结点个数
public int getLeafNode(TreeNode root){ if(root == null){ return 0; } if(root.left == null && root.right == null){ return 1; } return getLeafNode(root.left) + getLeafNode(root.right);}
求第k层结点个数
public int getKLevelNodes(TreeNode root, int k){ if(k < 1 || root == null){ return 0; } if(k == 1){ //当k为1时,说明只有根结点这一层,因此直接返回1即可 return 1; } //此时k不为1,就继续找下一层 return getKLevelNodes(root.left, k - 1) + getKLevelNodes(root.right, k - 1);}
求二叉树的高度
public int height(TreeNode root){ if(root == null){ return 0; } //树的高度 = 当前层 + 子树中的高度 return 1 + Math.max(height(root.left), height(root.right));}
判断二叉树中是否包含值val
public boolean contains(TreeNode<E> root, E val){ if(root == null){ return false; } if(root.val == val){ return true; } return contains(root.left, val) || contains(root.right, val);}
总结
该博客仅针对二叉树的基础部分做一个介绍,关于二叉树的内容、题目还有很多,之后会陆续发布关于前中后序以及层序遍历的详细解法,以及一些常考笔试、面试题的解答,如果有需要的话,就点个小小的关注吧~~