二叉树的模拟实现—Java数据结构
目录
一. 二叉树的存储
二. 二叉树的基本操作
1.手动快速创建一棵简单的二叉树
2. 二叉树的遍历
1. 前中后序遍历
2. 前序遍历的模拟实现
3. 中序遍历的模拟实现
4. 后序遍历的模拟实现
5.题解
6. 层序遍历
层序遍历的模拟实现 levelOrder()
模拟思路:
代码实现
3.模拟实现二叉树的基本操作
1.获取树中节点的个数 size()
2.获取叶子节点的个数 getLeafNodeCount()
3.获取第K层节点的个数 getKLevelNodeCount()
4.获取二叉树的高度 getHeight()
5.检测值为value的元素是否存在 findVal()
6.判断一棵树是不是完全二叉树 isCompleteTree()
如何判断完全二叉树
模拟实现
代码实现:
三. 二叉树模拟实现的完整代码:
一. 二叉树的存储
二叉树的存储结构分为:顺序存储和类似于链表的链式存储。
本篇博客介绍的是链式存储;
二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:
构造方法只需要给val赋值即可,因为new一个新节点时,我们也不知道左右孩子节点具体是谁。
二. 二叉树的基本操作
1.手动快速创建一棵简单的二叉树
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。
我们先手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
- 空树
- 非空:根节点,根节点的左子树、根节点的右子树组成的。
TreeNode 是 createTree() 方法的返回值类型,也是 BinaryTree 的内部类,所以要通过BinaryTree这个类名,调用TreeNode,并作为接收createTree方法的返回值的类型 。
所以,我们就实例化出了一棵以 A 为根节点 root 的二叉树:
2. 二叉树的遍历
1. 前中后序遍历
如果N代表根节点,L代表根节点的左子树,R代表根节点的右子树;
则根据遍历根节点的先后次序有以下遍历方式:
NLR:前序遍历(Preorder Traversal 亦称先序遍历)—访问根结点--->根的左子树--->根的右子树。
LNR:中序遍历(Inorder Traversal)—根的左子树--->根节点--->根的右子树。
LRN:后序遍历(Postorder Traversal)—根的左子树--->根的右子树--->根节点。
对于遍历二叉树,我们是通过递归来进行遍历的。
递归有三个最基本的条件:
第一,它要有一个起始条件,起始条件也是终止条件,什么时候结束递归;
第二,要调用自己本身;
第三,写出它的递推公式 。
当然,也可以不通过递归来遍历二叉树,接下来,是通过递归和非递归两种方法,完成对二叉树的前中后序遍历;以上图的二叉树为例子。
2. 前序遍历的模拟实现
访问根结点--->根的左子树--->根的右子树。
对遍历一棵二叉树的递归过程画图,可以更加方便我们了解递归的过程:
输出结果与画递归过程得到的结果相同:
二叉树的前序遍历
对于下面这个题目,我们要利用它的返回值,写递归前序遍历二叉树,和非递归前序遍历二叉树:
我们要实例化一个ArrayList,来存放即将要打印的节点 :
3. 中序遍历的模拟实现
根的左子树--->根节点--->根的右子树。
只有将左子树递归完,程序才会执行打印节点的代码:
输出结果与画递归过程得到的结果相同:
4. 后序遍历的模拟实现
根的左子树--->根的右子树--->根节点。
这里就不画图演示了。
5.题解
史上最全遍历二叉树详解
这一篇题解包含了递归和非递归,来遍历二叉树的方法,值得反复观看。
6. 层序遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。
设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发;
首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点;
以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
层序遍历的模拟实现 levelOrder()
模拟思路:
我们对上图这棵树进行层序遍历,
一开始root不为空,把A放进队列中,并且打印A
然后,每次从队列中弹出(Queue尾进头出)一个数据,给到cur
弹出数据给cur后,此时cur不为空;
就把root的左边B,放进队列中,同时打印B;
接下来,遍历到root的右边,右边不为空,将C插入队列,同时打印C
依次类推,接下来,让队列出一个元素B,给到cur,cur此时不为空,对应B节点
再看B的左边,左边B不为空,将D放进队列中,并且打印D;再看B的右边,B的右
边不为空,将E放入队列中,同时打印E
重复上面的操作:
接下来思考,判断二叉树层序遍历的结束条件是什么呢?
答:我们在上述的过程中,通过队列,来调整遍历的位置,出队列的元素赋值给 cur,同时打印cur;判断cur的左右节点是否为空,非空则让cur的左右节点入队列;所以当我们的队列为空时,说明已经层序遍历了整棵二叉树树 。
代码实现
代码实现,要先结合模拟实现的思路,可以跟着思路,来熟悉模拟实现的代码。
二叉树的层序遍历
3.模拟实现二叉树的基本操作
1.获取树中节点的个数 size()
方法一:遍历整颗二叉树
定义成员变量nodeSize,用递归的方法,遍历整棵二叉树,只要根节点不为空nodeSize++:
既然设置了nodeSize为成员变量,那么根本就没有用到size()的返回值。所以可以把方法返回值类型改成void,不用return :
方法二:整颗树的节点=左子树的节点+右子树的节点+1
在每次递归回溯的时候,都会为返回值+1 。
2.获取叶子节点的个数 getLeafNodeCount()
叶子节点:root.left==null&&root.right==null
如果符合叶子节点的定义,就让本次递归的返回值为1,整个递归的返回值之和,即为叶子节点的个数:
整个递归的顺序如下,只有出现return 1 的回溯过程,才会真正改变左右子树递归的返回值:
3.获取第K层节点的个数 getKLevelNodeCount()
遍历的思路也可以实现,但是这里用子问题的思路:
第K层节点的个数 = root左子树的第K-1层 + root右子树的第K-1层
这个方法的递归方式非常巧妙 ,传入的k为递归的次数,二叉树的层数在不断递归的过程中,k不断减小,当k==1时,返回1;最后返回K==1时,return 1的回溯次数:
4.获取二叉树的高度 getHeight()
我们还是使用子问题思路,来模拟实现获取二叉树的高度的方法:
获取树的最大高度
二叉树的高度 = Max(左子树的高度,右子树的高度) + 1
左子树的高度和右子树的高度,继续递归即可求出
递归回溯的过程如下:
获取二叉树的高度 getHeight() 方法:时间复杂度为 O(N),空间复杂度O(logN)
因为有N个节点,求最大高度的过程中,将每一个节点都遍历到了,所以时间复杂度为 O(N)
空间复杂度刚好等于树的高度,树的节点个数为 N,所以树的高度为 logN。
- 递归从下往上返回的时候,同时也要释放空间;如:递归左树的时候,栈帧会在回溯时把这些内存回收掉,再去递归右边;所以时间复杂度不是递归的次数,而是递归的深度
- 每一次递归,都是需要开辟空间的,那么连续递归到最深处, 开辟的空间就是最大的。
- 那么他刚好只是和树的高度重合了。 因为树的高度,也是递归的深度,而递归的深度,就是开辟空间最大的时候。
5.检测值为value的元素是否存在 findVal()
我们根据递归来模拟实现 findVal() 方法:
但是 ,如果我们在遍历子树的时候,如果当前子树的root左边为空,或者右边为空,root.left==null或者root.right==null,假设root .left 为空, 再递归一次, 第一步root为空就过不去了, 然后返回null了,这个写法没有利用返回值;
为了利用递归左右子树的返回值,我们再改进一下代码:
时间复杂度和空间复杂度的值和原理,与 getHeight() 方法相同。
递归回溯过程:
6.判断一棵树是不是完全二叉树 isCompleteTree()
如何判断完全二叉树
在正式开始模拟实现 isCompleteTree() 之前,我们先来讲一个简单的判断完全二叉树的小技巧:
根据层序遍历遍历二叉树的顺序,给下图的两棵二叉树的节点标好序号:
从A开始数数字, A1 B2 C3 D4 这种数字只有不间断就是完全二叉树,如第一颗二叉树;
在数下面这棵二叉树的过程中,在有E节点的情况下,发现缺失了E5,所以下面这棵二叉树不是完全二叉树。
当然,如果root刚开始就为空,这棵二叉树也是一棵完全二叉树。
模拟实现
完全二叉树的判断方法和刚刚程序遍历方法类似,区别是,遇到节点有null也要放入队列中:
弹出队列的元素为null时,cur为null,这时候队列都为null就说明这棵树是完全二叉树:
对于非完全二叉树:
在 cur 为空时,如果队列内还有非空元素,则这棵二叉树不是完全二叉树
代码实现:
如果root刚开始就为空,这棵二叉树也是一棵完全二叉树;root不为空,我们就先创建一个队列,并且先让root入队列:
这时候,我们就要按照刚刚层序遍历的思路,遍历这棵二叉树,区别就是,这里遍历到null,也要入队列;在cur=null的时候,判断队列中是否还有非空元素:
当前出队列的 cur 为非空的情况:
当前出队列的 cur 为空的情况:
下一步,开始遍历队列中,是否有非空元素:
这时候,我们要讲讲空的概念,哪怕队列中全放null,队列也不会为空
三. 二叉树模拟实现的完整代码:
static class TreeNode{public char val;public TreeNode left; //存储左孩子的引用public TreeNode right; //存储右孩子的引用public TreeNode(char val) {this.val = val;}}public TreeNode createTree(){TreeNode A=new TreeNode('A');TreeNode B=new TreeNode('B');TreeNode C=new TreeNode('C');TreeNode D=new TreeNode('D');TreeNode E=new TreeNode('E');TreeNode F=new TreeNode('F');TreeNode G=new TreeNode('G');TreeNode H=new TreeNode('H');A.left=B;A.right=C;B.left=D;B.right=E;C.left=F;C.right=G;E.right=H;//对于没赋值的节点,如D节点,默认为nullreturn A; //返回这棵二叉树的头节点}// 前序遍历public void preOrder(TreeNode root){if (root==null)return;System.out.print(root.val+ " ");preOrder(root.left);preOrder(root.right);}//递归前序遍历List<Character> list=new ArrayList<>();public List<Character> preorderTraversal(TreeNode root) {if (root==null)return list;//System.out.print(root.val+ " ");list.add(root.val);preOrder(root.left);preOrder(root.right);return list;}public List<Character> preorderTraversa2(TreeNode root) {List<Character> list1=new ArrayList<>();if (root==null)return list1;list1.add(root.val);List<Character> leftTree=preorderTraversa2(root.left);list1.addAll(leftTree);List<Character> rightTree=preorderTraversa2(root.right);list1.addAll(rightTree);return list1;}// 中序遍历public void inOrder(TreeNode root){if (root==null)return;inOrder(root.left);System.out.print(root.val+ " ");inOrder(root.right);}// 后序遍历public void postOrder(TreeNode root){if (root==null)return;postOrder(root.left);postOrder(root.right);System.out.print(root.val+ " ");}//层序遍历public void levelOrder(TreeNode root){if (root==null){return;}Queue<TreeNode> queue=new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){TreeNode cur=queue.poll();System.out.print(cur.val+" ");if (cur.left!=null)queue.offer(cur.left);if (cur.right!=null) queue.offer(cur.right);}}// 获取树中节点的个数public static int nodeSize;public void size1(TreeNode root){if (root==null)return ;nodeSize++;size(root.left);size(root.right);}public int size(TreeNode root){if (root==null)return 0;return size(root.left)+size(root.right)+1;}// 获取叶子节点的个数public int getLeafNodeCount(TreeNode root){if (root==null)return 0;if (root.left==null&&root.right==null){return 1;}return getLeafNodeCount(root.left)+getLeafNodeCount(root.right);}// 获取第K层节点的个数public 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);}// 获取二叉树的高度public int getHeight(TreeNode root){if (root==null)return 0;int leftTreeHeight=getHeight(root.left);int rightTreeHeight=getHeight(root.right);return Math.max(leftTreeHeight,rightTreeHeight)+1;}// 检测值为value的元素是否存在public TreeNode findVal(TreeNode root, char val){if (root==null)return null;if (root.val==val)return root;TreeNode leftT=findVal(root.left,val);if (leftT!=null){return leftT;}TreeNode rightT=findVal(root.right,val);if (rightT!=null){return rightT;}return null;}// 判断一棵树是不是完全二叉树public boolean isCompleteTree(TreeNode root){if (root==null)return true;Queue<TreeNode>queue=new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){TreeNode cur=queue.poll();if (cur!=null){queue.offer(cur.left);queue.offer(cur.right);}else {break;}}while (!queue.isEmpty()){if (queue.peek()!=null){return false;}queue.poll();}return true;}