数据结构之二叉树


一.树的定义

我们先来看看现实生活中树的概念.下面就是我们现实中树的定义如下:
在这里插入图片描述
无论多高多大的树,那也是从小到大、由根到叶、一点点成长起来的.俗话说十
年树木、 年树人,可一棵大树又何止是十年这样容易一一哈哈,说到哪里去了,我
们现在不是在上生物谍,而是要讲 种新的数据结构 一树。

二.树的概念和树的表现形式

2.1 树的概念

这里来解释树的概念,究竟什么是树呢?用文字来描述
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
图片如下:
在这里插入图片描述

树形结构中,子树之间不能有交集,否则就不是树形结构.

  • 结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为6
  • 树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为6
  • 叶子结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I…等节点为叶结点
  • 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点
  • 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点
  • 根结点:一棵树中,没有双亲结点的结点;如上图:A
  • 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推
  • 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4 树的
  • 非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G…等节点为分支结点
  • 兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点
  • 堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点
  • 结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先
  • 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
  • 森林:由m(m>=0)棵互不相交的树组成的集合称为森林

2.2 树的表现形式

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如:双亲表示法,
孩子表示法、孩子双亲表示法、孩子兄弟表示法等等。我们这里就简单的了解其中最常用的孩子兄弟表示法。

 static class TreeNode {
        public char val;//数据域
        public TreeNode left;//左孩子的引用
        public TreeNode right;//右孩子的引用
        public TreeNode(char val) {
            this.val = val;
        }
    }

在这里插入图片描述

三.二叉树简介

3.1 二叉树的定义:

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

在这里插入图片描述

  1. 二叉树不存在度大于2的结点
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
    对于任意的二叉树都是由以下几种情况复合而成的:
    在这里插入图片描述

3.2 二叉树与其他树结构的比较

这是两种特殊的二叉树

满二叉树

满二叉树是一种特殊的二叉树,它的每个节点都有0个或2个子节点,且所有叶节点都在同一层上。满二叉树的层数为h,节点总数为2^h-1。满二叉树是一种特殊的完全二叉树.
在这里插入图片描述

满二叉树的特点是结构非常规整,每层节点数都是满的,因此也叫做“完美二叉树”。

完全二叉树

完全二叉树是一种二叉树,除了最后一层,其它层的节点数都是满的,最后一层的节点都靠左排列。
在这里插入图片描述
完全二叉树的特点是结构也比较规整,最后一层的节点可能不满,但是一定是从左到右依次排列的。

四.二叉树的性质与表示

4.1 二叉树的性质:

性质1:
在一棵二叉树中,第i层上最多有2^(i-1)个节点。
在这里插入图片描述
性质2:
在一棵二叉树中,深度为k,最多有2^k-1个节点.
在这里插入图片描述
性质3
在一棵二叉树中,如果叶子节点数为n0,度数为2的节点数为n2,则n0=n2+1。
在这里插入图片描述
下面列举一个场景,大家还是看图片
终端结点数其实就是叶子结点数,而一棵二叉树,除了叶子结点外,剩下的就是度为1或2的结点数了,我们设n为度是1的结点数。则树T结点总数n=no+n1+n2。
在这里插入图片描述

性质4:
在这里插入图片描述
在这里插入图片描述

性质5:

在这里插入图片描述
下面是一个例子.
在这里插入图片描述

4.2 二叉树的链式存储结构与顺序存储结构的比较。

顺序存储

二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,井且结点的存储位
置,也就是数组的下标要能体现结点之间的逻辑关系,比如~亲与孩 的关系,左右
兄弟的关系等。
大家可以来看一下,完全二叉树的存储结构
在这里插入图片描述
将这棵树,放到数组中,如下:
在这里插入图片描述

另外对于一 般的二叉树,尽管层序编号不能反映逻辑关系,但也可以进行编号,只不过,把不存在的结点设置为 ^ 而已.
具体过程如下:
在这里插入图片描述
将这棵树,放到数组中,如下:
在这里插入图片描述

链式存储

既然顺序存储适用性不强,我们就要考虑链式存储结构。 二叉树每个结点最多有
两个孩子,所以为官设计一个数据域和两个指针域 较自然的想法, 我们称这样的
链表叫做二叉链表.
结构示意图,如图所示:
在这里插入图片描述

五. 二叉树的基本操作

5.1 二叉树的定义

对于java来说我们采用的是内部类去实现.
代码如下:

public class TestBinaryTree {
static class TreeNode {
        public char val;//数据域
        public TreeNode left;//左孩子的引用
        public TreeNode right;//右孩子的引用
        public TreeNode(char val) {
            this.val = val;
        }
    }
    }

至于为什么采用内部类的方式,我总结出来以下原因:
可以直接访问外部类的成员,简化内部类的定义。
至于为什么这么说,你看我下面的代码构思之后,就明白了.

5.2 二叉树的创建

这里我们简单粗暴一点,就直接手动创建即可,具体代码思路如下:

 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;
        return A;
    }

构造出来的二叉树如下:
在这里插入图片描述

5.2 前序遍历:根-左-右。

前序遍历的规则是先遍历根节点,再遍历左节点,然后遍历右节点,具体过程如下:
在这里插入图片描述
至于我们代码上的思路:
自然是采用递归,在采用递归之前,我们首先要明白递归的三个要点:

  1. 确定递归函数的参数和返回值
  2. 确定终⽌条件
  3. 确定单层递归的逻辑

我对这三个问题的回答:

  1. 参数:当前遍历的节点root
  2. 终止条件:root为空,直接返回
  3. 单层递归逻辑:
  • 先访问root节点
  • 递归遍历root的左子树
  • 递归遍历root的右子树

代码思路如下:

  public void preOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        System.out.print(root.val+" ");
        preOrder(root.left);
        preOrder(root.right);
    }

5.3 中序遍历:左-根-右。

中序遍历的规则是先遍历左节点,再遍历根节点,最后遍历右节点.
我们还是来看看下图的操作:
在这里插入图片描述
还是来回答这三个问题:

  1. 参数:当前遍历的节点root
  2. 终止条件:root为空,直接返回
  3. 单层递归逻辑:
  • 递归遍历root的左子树
  • 访问root节点
  • 递归遍历root的右子树

代码如下:

public void inOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.val+" ");
        inOrder(root.right);
    }

5.4 后序遍历:左-右-根。

后序遍历的规则是先遍历左节点,再遍历右节点,最后遍历根节点
看具体图示遍历过程
在这里插入图片描述

回答这三个基本的问题:

  1. 参数:当前遍历的节点root
  2. 终止条件:root为空,直接返回
  3. 单层递归逻辑:
  • 递归遍历root的左子树
  • 递归遍历root的右子树
  • 访问root节点

代码如下:

public void postOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        postOrder(root.left);
        postOrder(root.right);
        System.out.print(root.val+" ");

    }

5.5 层次遍历:逐层遍历结点。

二叉树的层次遍历顾名思义按照次序依次遍历,具体怎么个遍历法,大家看动图操作
在这里插入图片描述

咋说呢,我们要手动去实现层次遍历,我们要借助另一个数据结构,就是队列,队列的特性大家都知道,就是先进先出.我们要利用这个特性帮我们完成层次遍历,我下面会给出层次遍历的思路:

  1. 使用队列存储每一层的节点。
  2. 首先将根节点入队。
  3. 如果队列为空,则遍历结束。否则从队列中取出一个节点,访问之。
  4. 如果该节点有左子节点,则左子节点入队;如果有右子节点,则右子节点入队。
  5. 重复步骤3和4,直到队列为空。
    在这里插入图片描述

具体代码如下:

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);
            }
        }

5.6 三种遍历方式的非递归实现。

这里针对的遍历的非递归实现是前序遍历,中序遍历,后序遍历.
接下来我们将对这三种方式展开讨论

前序遍历(非递归)

我接下来给出前序遍历的思路:
我们要利用一个数据结构栈的先进后出特性,来实现这个前序遍历.
具体思路如下:

  1. 定义栈stack和指针cur初始指向根节点。
  2. 如果cur不为空,则访问节点cur,并将cur指向其左子节点,push到栈中。
  3. 如果cur为空且栈不为空,则弹出一个节点,并将cur指向其右子节点。
  4. 重复2和3直到cur为空且栈为空。

具体操作步骤

具体代码如下:

 public void preOrderNor(TreeNode root) {
        if(root == null) {
            return;
        }
        TreeNode cur = root;
        Deque<TreeNode> stack = new ArrayDeque<>();
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                System.out.print(cur.val + " ");
                cur = cur.left;
            }
            TreeNode top = stack.pop();
            cur = top.right;
        }
        System.out.println();
    }

中序遍历(非递归)

中序非递归的思路如下:

  1. 定义栈stack和指针cur,初始指向根节点。
  2. 如果cur不为空,则push到栈中,并cur指向其左子节点。
  3. 如果cur为空且栈不为空,则弹出一个节点,访问它,并将cur指向其右子节点。
  4. 重复2和3直到cur为空且栈为空。

具体代码思路如下:

public void inOrderNor(TreeNode root) {
        if(root == null) {
            return;
        }
        TreeNode cur = root;
        Deque<TreeNode> stack = new ArrayDeque<>();
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode top = stack.pop();
            System.out.print(top.val + " ");
            cur = top.right;
        }
        System.out.println();
    }

后序遍历(非递归)

后序遍历非递思路如下:

  1. 定义栈stack,指针cur和prev。cur初始指向根节点,prev初始为空。
  2. 如果cur不为空,则一直向左子树遍历,并将节点压入栈中。
  3. 如果cur为空,stack不为空,则判断栈顶节点top:
  • 如果top的右子树为空,或右子树已被访问过(prev指向它),则访问该节点,从stack中弹出,并将prev指向它。
  • 否则,令cur指向top的右子树,接着向左子树遍历。
  1. 重复步骤3,直到cur为空且stack为空,遍历结束。

具体的代码如下:

 public void postOrderNor(TreeNode root) {
        if(root == null) {
            return;
        }
        TreeNode cur = root;
        TreeNode prev = null;
        Deque<TreeNode> stack = new ArrayDeque<>();
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode top = stack.peek();
            if(top.right == null || top.right == prev) {
                System.out.print(top.val+" ");
                stack.pop();
                prev = top;
            }else {
                cur = top.right;
            }
        }
        System.out.println();
    }

5.7 获取叶子节点的个数

获取叶子节点的思路如下:
子问题思路:
什么是叶子节点?
左节点和右节点都为null就为叶子节点.
具体思路:

  1. 如果当前节点的左子树和右子树都为空,则当前节点即为叶子节点,将其加入结果列表。
  2. 如果当前节点的左子树不为空,则递归遍历左子树,获取左子树的叶子节点。
  3. 如果当前节点的右子树不为空,则递归遍历右子树,获取右子树的叶子节点。
  4. 合并左子树和右子树得到的叶子节点,得到当前二叉树的全部叶子节点。
  5. 不断递归,直到遍历完整棵二叉树,最终得到二叉树全部叶子节点

代码如下:

public int getLeafNodeCount(TreeNode root) {
        if(root == null) {
            return 0;
        }
        if(root.left == null && root.right == null){
            return 1;
        }
        int leftSize = getLeafNodeCount(root.left);
        int rightSize = getLeafNodeCount(root.right);
        return leftSize+rightSize;
    }

5.8 获取第K层节点的个数

具体思路如下:

  1. 如果K == 1,则返回根节点。因为根节点是二叉树的第1层。
  2. 如果当前节点的左子树或右子树不为空,且K > 1,则递归遍历左子树和右子树,获取各自第K-1层的节点个数。
  3. 当前层的节点个数 = 左子树第K-1层节点个数 + 右子树第K-1层节点个数。
  4. 递归终止条件是K < 1,此时返回0。
    具体实现代码如下:

代码如下:

public int getKLevelNodeCount(TreeNode root,int k) {
        if(root == null) {
            return 0;
        }
        if(k == 1) {
            return 1;
        }
        int leftSize = getKLevelNodeCount(root.left,k-1);
        int rightSize = getKLevelNodeCount(root.right,k-1);
        return leftSize+rightSize;
    }

5.9 获取二叉树的高度

具体思路如下:

  1. 如果树为空,高度为0。
  2. 如果左子树高度和右子树高度都不为0,则取左子树高度和右子树高度的最大值,加1作为当前树的高度。
  3. 如果只有左子树或右子树不为空,则取非空子树的高度加1作为当前树的高度。
  4. 不断递归,直到遍历完所有节点,得到整棵二叉树的高度。
 public int getHeight(TreeNode root) {
        if(root == null) {
            return 0;
        }

        return (getHeight(root.left) > getHeight(root.right)) ?
                (getHeight(root.left)+1):(getHeight(root.right)+1);
    }

5.10 检测值为value的元素是否存在

具体思路入下:

  1. 如果树为空,则返回False。
  2. 如果当前节点的值等于value,则返回True。
  3. 如果当前节点的值不等于value,则递归遍历左子树和右子树,检测是否存在值为value的节点。
  4. 如果左子树或右子树递归返回True,则返回True,否则返回False。
  5. 不断递归,直到遍历完整棵二叉树,如果任一子树返回True,则返回True,否则返回False。
 TreeNode find(TreeNode root, int val) {
        if(root == null) {
            return null;
        }
        if(root.val == val) {
            return root;
        }
        TreeNode leftTree = find(root.left,val);
        if(leftTree != null) {
            return leftTree;
        }
        TreeNode rightTree = find(root.right,val);
        if(rightTree != null) {
            return rightTree;
        }
        return null;//没有找到
    }

5.11 判断一棵树是不是完全二叉树

什么是完全二叉树,大家可以先来看看下面的图示:
在这里插入图片描述
完全二叉树只有两种情况,情况一:就是满二叉树,
情况二:最后一层叶子节点没有满。
知道了什么是完全二叉树之后,我们需要去判断一课树是不是完全二叉树,具体的思路如下:

  1. 空树视为完全二叉树,直接返回True。
  2. 使用一个队列存储二叉树节点,先入队根节点。
  3. 循环队列,如果当前节点不为空,则将其左右子节点入队。
  4. 如果遇到第一个为空的节点,则继续循环队列。
  5. 如果循环队列过程中,再遇到不为空的节点,则说明不是完全二叉树,返回False。
  6. 循环结束后,如果队列为空,说明是完全二叉树,返回True。
  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()) {
            TreeNode tmp = queue.poll();
            if(tmp != null) {
                return false;
            }
        }
        return true;
    }

六.二叉树的实际题目

1. 检查两颗树是否相同。

OJ链接
大概就是
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
首先我们先来划分不相同和相同的一个情况
在这里插入图片描述
具体思路如下:

  1. 如果只有一棵树为空,则两棵树不相同,返回False。
  2. 如果两棵树都为空,则相同,返回True。
  3. 如果两棵树的根节点值不相同,则不相同,返回False。
  4. 如果根节点相同,则递归检查两棵树的左子树是否相同。
  5. 如果左子树相同,则递归检查两棵树的右子树是否相同。
  6. 只有当左右子树也都相同时,则两棵树才相同,返回True。
 public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q != null || p != null && q == null) {
            return false;
        }
        //走到这里 要么都是空 要么都不是空
        if(p == null && q == null) {
            return true;
        }
        if(p.val != q.val) {
            return false;
        }
        //p q都不空 且 值一样
        return isSameTree(p.left,q.left)
                && isSameTree(p.right,q.right);
    }

2. 另一颗树的子树。

OJ链接

具体来说
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

目前判断是不是另一颗的子树有以下三种情况
在这里插入图片描述
具体思路如下:

  1. 判断是不是相同的树
  2. 判断是不是root的左子树
  3. 判断是不是root的右子树

代码如下:

 public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q != null || p != null && q == null) {
            return false;
        }
        //走到这里 要么都是空 要么都不是空
        if(p == null && q == null) {
            return true;
        }
        if(p.val != q.val) {
            return false;
        }
        //p q都不空 且 值一样
        return isSameTree(p.left,q.left)
                && isSameTree(p.right,q.right);
    }

   public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root == null || subRoot == null) {
            return false;
        }
        if(isSameTree(root,subRoot)) return true;
        if(isSubtree(root.left,subRoot)) return true;
        if(isSubtree(root.right,subRoot)) return true;
        return false;

    }

3. 翻转二叉树。

OJ链接

先带大家看一下具体的翻转过程究竟是什么样子的
在这里插入图片描述
具体思路:

  1. 如果根节点为空,直接返回。
  2. 交换根节点的左右子树。
  3. 递归翻转左子树。
  4. 递归翻转右子树。
  5. 返回翻转后的根节点。
    具体代码实现如下:
    具体代码如下:
 public TreeNode invertTree(TreeNode root) {
        if(root == null) {
            return null;
        }
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;

        invertTree(root.left);
        invertTree(root.right);

        return root;
    }

4. 判断一颗二叉树是否是平衡二叉树。

OJ链接
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。

具体下面的图示:
在这里插入图片描述

具体解决思路:
1.先求左边高度
2.再求右边高度
3.求高度之差,进行比较绝对值绝对不超过1
具体代码:

  public boolean isBalanced(TreeNode root) {
        if(root == null) {
            return true;
        }
        int leftH = maxDepth(root.left);
        int rightH = maxDepth(root.right);
        return Math.abs(leftH-rightH) < 2
                && isBalanced(root.left)
                && isBalanced(root.right);
    }
//求高度
     public int maxDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftHeight = maxDepth(root.left);
        int rightHeight = maxDepth(root.right);

        return (leftHeight > rightHeight) ?
                (leftHeight+1):(rightHeight+1);
    }
    }

5. 对称二叉树。

OJ链接
对称二叉树的结构如下:
在这里插入图片描述
我们的解题思路就是:
1.左子树的左树和右子树的右树是否相等
2.左子树的右树和右子树的左树是否相等
3.重复上面的俩个操作.
代码如下:

public boolean isSymmetric(TreeNode root) {
             if(root == null) return true;
        return isSymmetricChild(root.left,root.right);
    }

     public boolean isSymmetricChild(TreeNode leftTree,TreeNode rightTree) {
         
         //情况1
         if(leftTree != null && rightTree == null || leftTree == null && rightTree != null) {
            return false;
        }
        //情况2
         if(leftTree == null && rightTree == null) {
            return true;
        }
        //走到这里说明都不为空了
         if(leftTree.val != rightTree.val) {
            return false;
        }
         return isSymmetricChild(leftTree.left,rightTree.right)
                && isSymmetricChild(leftTree.right,rightTree.left);
        
        }

6. 二叉树的构建及遍历。

OJ链接
这道题的意思如下:
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
我们用具体的步骤去演示现在的内容.
在这里插入图片描述

具体的代码思路:

  1. 使用Scanner读取输入的字符串,每次读取一行。
  2. 根据输入的字符串构建二叉树,使用createTree递归构建。遇到#跳过,否则创建节点并递归构建左右子树。
  3. 中序遍历构建好的二叉树,输出节点的值。
  4. 重复步骤1-3,读取下一行输入并处理,直到没有更多输入。
class TreeNode {
    char val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(char val) {
        this.val = val;
    }
}
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        while (scan.hasNextLine()) {
            String str = scan.nextLine();
            TreeNode root = createTree(str);
            inOrder(root);
        }

    }
    public static int i = 0;
    public static TreeNode createTree(String str) {
        TreeNode root = null;
        if (str.charAt(i) != '#') {
            root = new TreeNode(str.charAt(i));
            i++;
            root.left = createTree(str);
            root.right = createTree(str);
        } else {
            i++;
        }
        return root;
    }
    public static void inOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
    }


}

7. 二叉树的分层遍历 。

OJ链接

在介绍这个题目之前,我们还要了解一下,什么是二叉树的分层遍历
规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。如图所示,遍历的顺序为:ABCDEFGHI。
在这里插入图片描述

接下来我们先来实现最简单的分层遍历,再根据这个简单的分层遍历去思考在线oj的题目
具体思路如下:

  1. 根节点入队
  2. 输出队头节点,并将其非空子节点入队
  3. 队头出队,重复步骤2
  4. 直到队列为空,遍历结束

动画效果如下:
在这里插入图片描述

具体代码如下:

 public void levelOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        //1.根节点入队
        queue.offer(root);
        while (!queue.isEmpty()) {
        //2.队头出队
            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);
            }
        }
    }

看了上面的题解之后,我们来解决上面的在线oj.
具体思路:

  1. 创建一个队列queue和一个列表list来存储结果。
  2. 首先将根节点root加入队列。
  3. 然后进入一个循环,循环次数取决于队列的大小。
  4. 在每次循环中,从队列中弹出一个节点,并将其值添加到临时列表tmp中。
  5. 然后将该节点的左右子节点(如果存在)加入队列。
  6. 循环完成后,将临时列表tmp添加到结果列表list中。
  7. 重复步骤3-6,直到队列为空。
  8. 返回结果列表list。
    具体代码:
public List<List<Integer>> levelOrder2(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        if(root == null) {
            return list;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> tmp = new ArrayList<>();
            while (size != 0) {
                TreeNode cur = queue.poll();
                //System.out.print(cur.val + " ");
                //tmp.add(cur.val);
                size--;
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            list.add(tmp);
        }
        return list;
    }

8. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。

先来对问题进行一定的分析.
目前公共祖先出现的情况我们分为以下三种情况.
OJ链接
在这里插入图片描述

具体思路:

  1. 首先判断当前根节点root是否是p或q其中的一个,如果是则直接返回root。
  2. 然后递归地查找左子树和右子树。
  • 如果左子树返回的节点和右子树返回的节点都不为空,说明p和q分别在左子树和右子树,所以当前root是最近公共祖先,返回root。
  • 如果左子树返回的节点不为空,则返回左子树返回的节点。
  • 如果右子树返回的节点不为空,则返回右子树返回的节点。
  1. 如果左右子树都返回空,则说明当前树中不存在p和q,返回null。
  2. 通过这样的递归,如果p和q的最近公共祖先节点较深,则会先找到更深的那个,否则会直接在判断根节点的时候返回根节点。
    具体代码:
 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) {
            return null;
        }
        //先判断根节点  是不是其中的一个
        if(root == p || root == q) {
            return root;
        }
        TreeNode leftRet = lowestCommonAncestor(root.left,p,q);
        TreeNode rightRet = lowestCommonAncestor(root.right,p,q);

        if(leftRet != null && rightRet != null) {
            return root;
        }else if(leftRet != null) {
            return leftRet;
        }else if( rightRet != null ){
            return rightRet;
        }

        return null;
    }

具体对代码的细节做出一些解释
在这里插入图片描述

9. 根据一棵树的前序遍历与中序遍历构造二叉树。

OJ链接
在写代码之前,我们先来看看,具体我们怎么通过分析得到构造的二叉树.
在这里插入图片描述

代码具体思路:

  1. 在前序遍历找到根节点
  2. 在中序遍历中找到根节点root的位置,则root的左子树位于中序遍历中root的左边,右子树位于右边。
  3. 递归地构造左子树和右子树。
    具体的代码如下:
class Solution {

    class TreeNode {
      int val;
      TreeNode left;
      TreeNode right;
      TreeNode() {}
      TreeNode(int val) { this.val = val; }
      TreeNode(int val, TreeNode left, TreeNode right) {
          this.val = val;
          this.left = left;
          this.right = right;
      }
    }
    public int i = 0;
    public TreeNode buildTree(int[] preorder, int[] inorder) {

        return buildTreeChild(preorder,inorder,0,inorder.length-1);
    }

    public TreeNode buildTreeChild(int[] preorder, int[] inorder,
    int inbegin,int inend) {
        if(inbegin > inend) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[i]);
        //找到当前根,在中序遍历的位置
        int rootIndex = findIndex(inorder,inbegin,inend,preorder[i]);
        i++;
        root.left = buildTreeChild(preorder,inorder,inbegin,rootIndex-1);
        root.right = buildTreeChild(preorder,inorder,rootIndex+1,inend);
        return root;
    }

    private int findIndex( int[] inorder,int inbegin,int inend, int key) {
        for(int i = inbegin;i <= inend; i++) {
            if(inorder[i] == key) {
                return i;
            }
        }
        return -1;
    }

10. 根据一棵树的中序遍历与后序遍历构造二叉树。

OJ链接
在写代码之前,我们先来看看,具体我们怎么通过分析得到构造的二叉树.
在这里插入图片描述

具体思路:

  1. 在后序遍历找到根节点
  2. 在中序遍历中找到根节点root的位置,则root的左子树位于中序遍历中root的左边,右子树位于右边。
  3. 递归地构造左子树和右子树。

具体代码:

  class Solution {
        public int i = 0;
        public TreeNode buildTree(int[] inorder, int[] postorder) {
            i = postorder.length-1;
            return buildTreeChild(postorder,inorder,0,inorder.length-1);
        }

        public TreeNode buildTreeChild(int[] postorder, int[] inorder,
                                       int inbegin,int inend) {
            if(inbegin > inend) {
                return null;
            }
            TreeNode root = new TreeNode(postorder[i]);
            //找到当前根,在中序遍历的位置
            int rootIndex = findIndex(inorder,inbegin,inend,postorder[i]);
            i--;
            root.right = buildTreeChild(postorder,inorder,rootIndex+1,inend);
            root.left = buildTreeChild(postorder,inorder,inbegin,rootIndex-1);
            return root;
        }

        private int findIndex( int[] inorder,int inbegin,int inend, int key) {
            for(int i = inbegin;i <= inend; i++) {
                if(inorder[i] == key) {
                    return i;
                }
            }
            return -1;
        }
    }


}

11. 二叉树创建字符串。

OJ链接
具体分析过程
在这里插入图片描述

我们这里
具体思路:

  1. 首先使用StringBuilder来构建字符串。
  2. 使用递归的方法前序遍历二叉树的每个节点。
  3. 如果当前节点t为空,则直接返回。
  4. 如果当前节点t不为空,则添加对应的"(“和”)",以及节点的值t.val。
  5. 如果当前节点的左子节点t.left不为空,则递归遍历左子树,并添加对应的"(“和”)"。
  • 如果左子节点为空而右子节点不为空,则添加"()"表示左子树为空。
  1. 如果当前节点的右子节点t.right为空,则直接返回。
  • 如果右子节点不为空,则递归遍历右子树,并添加对应的"(“和”)"。
  1. 递归遍历完成后,通过StringBuilder.toString()得到最终的字符串。
    具体代码:
  public String tree2str(TreeNode root) {
        if(root == null ){
            return null;
        }
        StringBuilder stringBuilder=new StringBuilder();
        tree2strChilde(root,stringBuilder);
        return  stringBuilder.toString();
    }
    public void tree2strChilde(TreeNode t,StringBuilder stringBuilder) {

        if(t == null) {
            return;
        }

        stringBuilder.append(t.val);
        if(t.left != null) {
            stringBuilder.append("(");
            tree2strChilde(t.left,stringBuilder);
            stringBuilder.append(")");
        }else {
            //左边为空了
            if(t.right != null) {
                //右边不为空
                stringBuilder.append("()");
            }else {
                //右边为空
                return ;
            }
        }

        if(t.right == null) {
            return;
        }else {
            stringBuilder.append("(");
            tree2strChilde(t.right,stringBuilder);
            stringBuilder.append(")");
        }

    }

五. 总结与扩展

树是一种非线性数据结构,由节点和边组成。每个节点最多只有一个父节点,但可以有多个子节点。二叉树是树的一种特殊形式,每个节点最多只有两个子节点。树和二叉树在算法和数据结构中有广泛的应用,例如搜索、排序、数据库、编译器等。它们也可以用于解决各种实际问题,如计算机网络、生物学、图形学等。

扩展:
树和二叉树是计算机科学中非常重要的基础数据结构,它们有许多扩展形式和应用。例如:

平衡树:平衡树是一种特殊的二叉树,它可以保证树的高度始终在一个较小的范围内,从而提高了树的效率。常见的平衡树包括AVL树、红黑树等。

B树和B+树:B树和B+树是一种多叉树,它们被广泛应用于文件系统和数据库中,可以快速地进行插入、删除和查找等操作。

Trie树:Trie树是一种多叉树,它用于存储字符串集合,可以快速地进行字符串匹配和前缀搜索等操作。

堆:堆是一种特殊的树形数据结构,它满足堆序性质,可以用于排序和优先级队列等场景。常见的堆包括最大堆和最小堆等。

赫夫曼树:赫夫曼树是一种特殊的二叉树,它被广泛应用于数据压缩中,可以将数据压缩成更小的体积。

除此之外,树和二叉树还有许多其他的应用,如图形学中的空间分区、计算机网络中的路由算法、生物学中的系统演化等。因此,学习树和二叉树对于理解计算机科学中的许多问题具有重要意义。

期待我们后续的学习,小伙伴们!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忘忧记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值