前言❤️❤️
hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹
这篇博客会解析二叉树几个基础操作,包括求二叉树的结点个数、第k层结点、高度、结点查找、完全二叉树的判断
这些操作大部分都是通过递归的方式来实现的,
这个专栏的数据结构是代码都是用Java来写的,JavaSE专栏现在已经全部更新完成,铁汁们复习基础知识时非常推荐使用,可以试一下💪💪💪
🎇个人主页:洋不写bug的博客
🎇所属专栏:数据结构专栏
🎇复习Java基础知识:Java学习之旅,从入门到进阶
🎇铁汁们对于数据结构基础的各种核心知识(不太常用的也有😆),都可以在上面的数据结构专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~
1,基础代码
- 在写方法之前,首先要把二叉树创建出来,创建一个Node类,表示二叉树的结点,再写个createTree方法,创建一个二叉树,createTree方法返回的是二叉树的根结点,代码如下:
java
public class Node {
public String val;
public Node left;
public Node right;
public Node(String val) {
this.val = val;
this.left = null;
this.right = null;
}
@Override
public String toString() {
return "Node{" +
"val='" + val + '\'' +
", left=" + left +
", right=" + right +
'}';
}
}

java
public class Test {
public static Node createTree(){
Node a = new Node("A");
Node b = new Node("B");
Node c = new Node("C");
Node d = new Node("D");
Node e = new Node("E");
Node f = new Node("F");
Node g = new Node("G");
a.left = b;
a.right = c;
b.left = d;
b.right = e;
e.left = g;
c.right = f;
return a;
}
public static void main(String[] args) {
Node root = createTree();
}
}
2,求二叉树的结点个数
- 需要写个size方法,传入二叉树的根结点,就能得出二叉树的结点个数
- 铁汁们想下,这个是不是跟二叉树的遍历是类似的,只不过每次访问结点的操作改为size++,使用前序、中序、后序都是可以的,这里我们就使用前序,代码如下:
java
public static int size(Node root){
int size = 0;
if(root == null){
return 0;
}
size++;
size(root.left);
size(root.right);
return size;
}
-
这个代码大概分析下,似乎是没问题的,搞个简单的树结构代入下,size++确实是执行了三次
-

-
接下来就在main方法中打印一下,createTree方法里面创建的树有7个结点,预期打印的结果应该是7,但是打印结果却是1
java
public static void main(String[] args) {
Node root = createTree();
System.out.println(size(root));
}

- 这是因为调用size方法时,每次递归,都会int size = 0,都会创建一个全新的size变量,所以不是在原来的size的基础上++的,因此打印结果就是1
- 对于这种情况,有两种解决方案,第一种就是使用成员变量(前面size写在方法中属于是局部变量),这样再次运行main方法,打印的就是7
java
private static int size = 0;
public static int size(Node root) {
if (root == null) {
return 0;
}
size++;
size(root.left);
size(root.right);
return size;
}
- 这种写法有个很大的弊端,就是一次main方法执行只能使用一次,第二次使用结果一定是错的,因为使用完size方法后,size的值不会更新到0
在main方法中,再传入一个单独的结点,判断长度,size是不是从0开始加的,是在上棵树的基础上加的(使用后传入个单独的结点,结果就会打印8)
java
public static void main(String[] args) {
Node root = createTree();
System.out.println(size(root));
Node root2 = new Node("a");
System.out.println(size(root2));
}

- 使用成员变量这种做法,在做OJ题的时候也是不行的,因为OJ题会有很多判例,除了第一个判例结果是对的,后面的就都是错的
- 比较推荐的写法就是还是使用递归,通过返回值把当前子树的递归结果返回到上层方法中,上层方法再进行累加
java
public static int size(Node root){
if(root == null){
return 0;
}
int rootSize = 1;
int leftSize = size(root.left);
int rightSize = size(root.right);
return rootSize + leftSize + rightSize;
}
- 这个代码可以这样来理解,一棵树的size由三个部分组成:根结点(结点个数为1)、左子树的结点个数、右子树的结点个数
- 左子树和右子树的结点个数也按同样的方法来算,也是由子树根结点,子树根结点的左子树结点个数,子树根结点的右子树结点个数组成,就这样递归来计算
-
画图分析下这个方法的递归过程
-

-
在main方法中测试下
java
public static void main(String[] args) {
Node root = createTree();
System.out.println(size(root));
Node root2 = new Node("a");
System.out.println(size(root2));
}

3,二叉树第k层结点的个数
- 写一个getKLevelNodeCount方法,传入二叉树的根结点和k,就能返回该二叉树第k层结点的个数、
-
当k < 1的时候,就是非法输入,当k = 1的时候,如果树不为空,那结点个数就是1,我们需要分析当k > 1时,结点的个数如何计算
前面的size方法就是通过递归不断的缩小问题规模,直到问题规模缩小为1,也就是递归到size(叶子结点),这个题目也可以用类似的思想来解决 -
求二叉树的第k层的结点个数,可以转化为求二叉树左子树的k - 1层的结点个数 + 二叉树右子树的k - 1层的结点个数;接着可以继续缩小问题规模,就比如二叉树左子树的k - 1层的结点个数,等于二叉树的左子树的左子树的k - 1层结点个数,就这样一直递归去缩小问题规模
-
就比如求这个树的第3层结点个数,就等于B这个子树第2层的结点个数 + C这个子树第2层的结点个数;继续递归缩小规模,B这个子树的第二层结点个数就等于D这个子树的第1层结点个数 + E这个子树第1层的结点个数
-

-
代码如下
java
public static int getKLevelNodeCount(Node root,int k){
if(root == null || k < 1){
return 0;
}
if(k == 1){
return 1;
}
return getKLevelNodeCount(root.left,k - 1) + getKLevelNodeCount(root.right,k - 1);
}
4,获取二叉树的高度
-
二叉树的高度就是从根结点出发到任意结点,路径的最大长度
-

-
这个题目依然是是使用递归逐步减小问题规模
树A的长度 = 1 + MAX(树B的长度,树C的长度)
树B的长度 = 1 + MAX(树D的长度,树E的长度) -
递归过程想明白,代码写起来就特别简单
java
public static int getHeight(Node root){
if(root == null){
return 0;
}
return 1 + Math.max(getHeight(root.left),getHeight(root.right));
}
- 这里博主就不再画递归过程图了,对递归掌握不太好的铁汁,可以自己在画图板上画下递归调用图,就能很好的理解了💪🐵
5,查找树中值为value的结点
- 这个还是个递归的过程,先判根结点的val是否是value,如果不是,就在左子树中递归来找,如果左子树递归找完了,还没找到,就在右子树中递归来找,如果右子树也递归完没找到,就说明值为value的结点不存在,返回null
java
public static Node find(Node root,String value){
if(root == null){
return null;
}
if(root.val.equals(value)){
return root;
}
Node leftResult = find(root.left,value);
if(leftResult != null){
return leftResult;
}
Node rightResult = find(root.right,value);
if(rightResult != null){
return rightResult;
}
return null;
}
- 在右子树中,如果查找不到,就直接返回null了,这里代码写的可以再精简一些
java
public static Node find(Node root,String value){
if(root == null){
return null;
}
if(root.val.equals(value)){
return root;
}
Node leftResult = find(root.left,value);
if(leftResult != null){
return leftResult;
}
return find(root.right,value);
}
-
画图模拟下递归过程,要查找value为"D"的结点
-

-
在Node类中重写个toString方法,就可以在main方法中打印结点的信息,这里就直接alter + insert键,插入自动生成的
java
public String toString() {
return "Node{" +
"val='" + val + '\'' +
", left=" + left +
", right=" + right +
'}';
}
-
在main方法中查找元素值为B的结点,打印这个结点的信息(toString方法中也打印了这个结点left和right结点的信息)
-
javapublic static void main(String[] args) { Node root = createTree(); System.out.println(find(root,"B")); }
6,判断完全二叉树
-
完全二叉树的特点就是在每一层,都是从左往右铺结点的,铺完铺不完无所谓,重要的是中间不能跳过,根据完全二叉树的特征,我们就可以分析出这里应该采用层序遍历来遍历二叉树
-

-
单独的层序遍历代码如下,就是通过队列的方式进行层序遍历,写这个方法的前提理解层序遍历代码的原理(上篇博客中有代码的详细解析🐵)
java
public static void levelOrder(Node root) {
if (root == null) {
return;
}
Queue<Node> queue = new ArrayDeque<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node cur = queue.poll();
System.out.printf(cur.val + " ");
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
-
接下来就分析下如何判断,层序遍历时是把结点放到队列中去,接着出队列打印,再把这个结点的左结点和右结点加到队列中去
如下图,B结点在出队列后,队列中下个是C结点,下面这几种情况下,只要C结点有子结点,这个树就一定不是完全二叉树 -

- B结点的左右子树都为空(B.left == null && B.right == null)
- B结点的左子树不为空,右子树为空(B.left != null && B.right == null)
- B结点的左子树为空,右子树不为空(B.left == null && B.right != null),这种情况也没必要再把C出队列判断了,直接返回false即可
-
那经过前面的分析,可以设定个第一阶段和第二阶段,在层序遍历时,对于出队列的结点进行判断,分为以下几种情况
- 左右子树都为空,进入第二阶段;
- 左子树不为空,右子树为空,进入第二阶段
- 如果左子树为空,右子树不为空,那就直接返回false即可
- 左右子树都不为空,就继续正常的进行层序遍历
-
下个结点出队列时,判断是第一阶段还是第二阶段
-
如果为第二阶段,想要满足完全二叉树,那下个结点的左子树和右子树都必须为空,不满足的话,就直接返回false
-
如果为第一阶段,那就继续按照上面的四种情况进行判断
- 写代码时注意,空树也属于是完全二叉树,所以如果root为null,就返回true,维护一个boolean类型的变量isFirstStep,来确定当前是第一阶段还是第二阶段,如果满足了进入第二阶段的条件,就把isFirstStep的值改为false
- 如果层序遍历完都没有返回false,那就返回true
java
public static boolean isComplete(Node root){
if(root == null){
return true;
}
Queue<Node> queue = new ArrayDeque<>();
boolean isFirstStep = true;
queue.offer(root);
while(!queue.isEmpty()){
Node cur = queue.poll();
if(isFirstStep){
if(cur.left == null && cur.right == null){
isFirstStep = false;
}else if(cur.left != null && cur.right == null){
isFirstStep = false;
queue.offer(cur.left);
}else if(cur.left == null && cur.right != null){
return false;
}else{
queue.offer(cur.left);
queue.offer(cur.right);
}
}else{
if(cur.left != null || cur.right != null){
return false;
}
}
}
return true;
}
- 接着修改下createTree方法,创建一个完全二叉树,在main方法中试一下(铁汁们可以自己再创建个非完全二叉树,试一下)
java
public static Node createTree() {
Node a = new Node("A");
Node b = new Node("B");
Node c = new Node("C");
Node d = new Node("D");
Node e = new Node("E");
Node f = new Node("F");
Node g = new Node("G");
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
return a;
}
java
public static void main(String[] args) {
Node root = createTree();
System.out.println(isComplete(root));
}

结语
-
递归算是二叉树解题的灵魂,很多复杂问题都可以通过递归逐步缩小问题规模,递归的代码写起来也特别简短,但要分析透递归的过程却不容易,需要多画几次图,细品下递归过程,这样分析递归的能力就会逐渐提升💪💪💪
-
以上就是今天的所有内容啦~完结撒花~🥳🎉🎉

z
