【java数据结构】二叉树
- 一、 认识二叉树
- 1.1 二叉树的概念
- 1.2 二叉树的特性:
- 1.3 两种特殊的二叉树:
- 1.4 二叉树的性质:
- 1.5 二叉树的存储:
- 二、 实现二叉树
- 2.1 二叉树节点的定义:
- 2.2 二叉树的基本操作:
- 获取树中节点的个数
- 获取叶子节点的个数
- 获取第K层节点的个数
- 获取二叉树的高度
- 检测值为value的元素是否存在
- 判断一棵树是不是完全二叉树
- 2.3 二叉树的遍历:
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
此篇博客希望对你有所帮助(帮助你了解二叉树,二叉树主要运用递归,有那块代码不懂或者理解不了的,一定要进行画图去理解),不懂的或有错误的也可在评论区留言,错误必改评论必回!!!持续关注,下一篇博客是二叉树面试题!!!整篇博客的代码都在Gitee中(代码链接放在文章结尾)。
一、 认识二叉树
1.1 二叉树的概念
二叉树是一个有限的节点集合,这个集合可能是空的(此时称为空二叉树),或者由一个根节点以及两个互不相交的、分别被称为左子树和右子树的二叉树所组成。
1.2 二叉树的特性:
- 每个节点至多有两个子节点,分别被称为左子节点和右子节点。
- 左子树和右子树的次序不能颠倒。
- 二叉树是有序树,因为即使树中节点没有值,其左右子树的区分也仍然有意义。
对于任意二叉树都是由以下几种情况复合而成:
1.3 两种特殊的二叉树:
- 满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。也就是说,如果一棵二叉树的层数为K,且结点总数是(2^k)-1 ,则它就是满二叉树。
- 完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
1.4 二叉树的性质:
二叉树(Binary Tree)具有一些重要的性质,这些性质对于理解和操作二叉树非常有帮助。下面是二叉树的一些常见性质:
- 每个节点最多有两个子节点:每个节点最多有一个左子节点和一个右子节点。这意味着一个节点的度数(Degree)最大为2。
- 左子树和右子树的顺序是固定的:在二叉树中,左子树位于父节点的左侧,右子树位于父节点的右侧。改变左右子树的顺序将产生不同的二叉树。
- 节点的度数:节点的度数是指该节点的子节点数量。二叉树中,节点的度数最大为2,即一个节点最多有两个子节点。
- 叶节点(叶子节点):叶节点是没有子节点的节点,也可以称为终端节点。
- 节点的层级(Level):根节点的层级为1,其余节点的层级为其父节点的层级加1。
- 树的高度(Height):树的高度是从根节点到最远叶节点的最长路径上的边数。叶节点的高度为0,空树的高度为-1。
- 完全二叉树(Complete Binary Tree)性质:在完全二叉树中,除了最后一层外,其他层的节点都是满的,且最后一层的节点都尽量靠左排列。
- 满二叉树(Full Binary Tree)性质:在满二叉树中,除了叶节点外,每个节点都有两个子节点。
- 二叉搜索树(Binary Search Tree)性质:二叉搜索树是一种特殊的二叉树,它满足以下性质:对于树中的每个节点,其左子树中的所有节点都小于该节点的值,而其右子树中的所有节点都大于该节点的值。
- 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1) (i > 0)个结点。
- 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (2^k) - 1 (k>=0)。
- 对任何一棵二叉树, 如果其叶结点个数为 n0,度为2的非叶结点个数为 n2,则有 n0 = n2 + 1。
- 具有n个结点的完全二叉树的深度k为log2 (n + 1) 上取整。
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为 i 的结点有:
- 若 i > 0,双亲序号:(i - 1) / 2;i = 0,i 为根结点编号,无双亲结点。
- 若 2i + 1< n,左孩子序号:2i + 1,否则无左孩子。
- 若 2i + 2 < n,右孩子序号:2i + 2,否则无右孩子。
1.5 二叉树的存储:
- 链式存储法(链表表示法)
链式存储法使用节点(Node)来存储二叉树的每个元素,每个节点包含三个部分:
数据域(data):存储节点的值。
左指针(left):指向左子节点。
右指针(right):指向右子节点。
这种方法的优点是可以灵活地表示任意形状的二叉树,节省空间(不需要为空的子节点分配空间)。缺点是需要额外的指针空间,并且遍历二叉树时可能涉及频繁的指针操作。
- 顺序存储法(数组表示法)
顺序存储法使用一个数组来存储二叉树的节点。对于完全二叉树,节点在数组中的位置可以通过以下公式确定:
对于根节点,其索引为 0。
对于节点索引为 i 的左子节点,其索引为 2i + 1。
对于节点索引为 i 的右子节点,其索引为 2i +2。
这种方法的优点是操作简单,通过索引关系可以直接访问任意节点的子节点和父节点。缺点是对于非完全二叉树,会浪费空间(因为数组中需要填充空节点)。
二、 实现二叉树
2.1 二叉树节点的定义:
static class TreeNode{public char val;public TreeNode left;//左孩子的引用public TreeNode right;//右孩子的引用public TreeNode(char val) {this.val = val;}}
2.2 二叉树的基本操作:
// 获取树中节点的个数int size(TreeNode root){}// 获取叶子节点的个数int getLeafNodeCount(TreeNode root){}// 子问题思路-求叶子结点个数
// 获取第K层节点的个数int getKLevelNodeCount(TreeNode root,int k){}// 获取二叉树的高度int getHeight(TreeNode root){}// 检测值为value的元素是否存在TreeNode find(TreeNode root, int val){}// 判断一棵树是不是完全二叉树boolean isCompleteTree(TreeNode root){}
获取树中节点的个数
代码思路:
1.判断数是否为空。为空节点个数为0。
2.定义一个全局int型变量len,因为需要递归,如果将len定义为局部变量,在每次递归时len都是我们给定的初始值。
// 获取树中节点的个数public int len = 0;int size(TreeNode root) {if (root == null) {return 0;}len++;size(root.left);size(root.right);return len;}
获取叶子节点的个数
代码思路:
两种解决方案:
1.先去判断树是否为空
(1)
1.定义一个全局变量size,和上面思路一样。
2.通过叶子节点的概念,我们熟知,我们需要递归每一个节点,然后去判断节点的度是否为0,为0,则是叶子节点,否则,反之。
(2)
1.不去定义全局变量,直接递归。
2.递归到每个节点都去判断一次节点的度是否为0,如果为0,则返回到上次递归语句中。
// 获取叶子节点的个数public int size = 0;int getLeafNodeCount(TreeNode root) {if (root == null) {return 0;}getLeafNodeCount(root.left);if (root.right == null) {size++;}getLeafNodeCount(root.right);return size;}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层节点的个数
代码思路:
1.先去判断代码是否为空
2.因为是获得第K层的节点个数,我们可以采用逆序的思想:
将根那一层当成第K层,然后进行递归,每递归一次K-1,当递归到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);}
获取二叉树的高度
代码思路:
1.先判断树是否为空
2.分别对每个节点进行左递归和右递归
3.判断这个节点左递归和右递归那个递归的次数多,返回那个值,最后返回的值就是树的高度。
// // 获取二叉树的高度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的元素是否存在
代码思路:
1.先判断树是否为空
2.然后判断根节点是否为value值,如果不是,则进行递归
3.如果找到value值,则需要定义一个节点ret来接受找到的value值的节点,进行一步一步返回。这里返回的将一直是找到的ret节点
// // 检测值为value的元素是否存在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;}
判断一棵树是不是完全二叉树
代码思路:
1.这里需要借助所学的队列(先进先出)
2.首先判断树是否为空
3.将根节点先存入到队列当中,这里需要定义一个cur来标记出队列的节点
4.当出一个节点,就将这个节点的左右孩子节点存入队列中,不管左右孩子是否为空
这里值得注意的是:存入队列当时中的节点为null,queue.isEmpty()不为空的(判断 的是队列中的元素个数)
5.当cur为null时,跳出queue.isEmpty()循环,这个时候判断队列当中是否还有除了nul,以为的其他元素,如果有其他元素,则这就不是一个完全二叉树,反之,则不是完全二叉树。
// // 判断一棵树是不是完全二叉树boolean isCompleteTree(TreeNode root){Queue<TreeNode> queue=new LinkedList<>();if(root==null){return true;}queue.offer(root);TreeNode cur;while(!queue.isEmpty()){cur=queue.poll();if(cur!=null) {queue.offer(cur.left);queue.offer(cur.right);}else{break;}}while(!queue.isEmpty()){if(queue.peek()==null){queue.poll();}else{return false;}}return true;}
完全二叉树图解:
非完全二叉树图解:
2.3 二叉树的遍历:
遍历是访问树中所有节点的过程,主要有以下几种方式:
- 前序遍历(Preorder Traversal):根节点 -> 左子树 -> 右子树
- 中序遍历(Inorder Traversal):左子树 -> 根节点 -> 右子树(对于二叉搜索树,这是升序访问所有节点的方式)
- 后序遍历(Postorder Traversal):左子树 -> 右子树 -> 根节点
- 层次遍历(Level Order Traversal):按层次从上到下、从左到右访问节点(通常使用队列实现)。
举例:
前序遍历:A B C D E F
中序遍历:C B D A E F
后序遍历 :C D B F E A
层次遍历 :A B E C D F
前序遍历
// 前序遍历void preOrder(TreeNode root){if(root==null){return;}System.out.println(root.val+" ");preOrder(root.left);preOrder(root.right);}
中序遍历
// 中序遍历void inOrder(TreeNode root){if(root==null){return;}inOrder(root.left);System.out.println(root.val+" ");inOrder(root.right);}
后序遍历
// 后序遍历void postOrder(TreeNode root){if(root==null){return;}postOrder(root.left);postOrder(root.right);System.out.println(root.val+" ");}
层序遍历
代码思路:
1.需要借助对列来实现。
2.首先先判断树是否为空。
3.不为空,先将根节点存进去,然后定义一个节点cur来接受从队列中出来的节点,判断弹出的节点左右孩子节点是否为空,不为空的存入到队列当中,打印弹出的元素。
// //层序遍历void levelOrder(TreeNode root){Queue<TreeNode> queue=new LinkedList<>();if(root==null){return;}queue.offer(root);TreeNode cur;while(!queue.isEmpty()){cur=queue.poll();if(cur.left!=null){queue.offer(cur.left);}if(root.right!=null){queue.offer(cur.right);}System.out.print(cur.val+" ");}}
此篇博客的代码!!!