[Java/数据结构]树的基本概念、二叉树的创建和遍历

画师:竹取工坊

大佬们好!我是Mem0rin!现在正在准备自学转码。

如果我的文章对你有帮助的话,欢迎关注我的主页Mem0rin,欢迎互三,一起进步!


文章目录


主要内容是如何构造出二叉树的数据结构,如何通过递归和非递归的方式完成对二叉树的四种遍历方式。

一、树的基本概念

1. 什么是树

先看这么一张树的图片,从根延伸出枝干,枝干再继续分成多个叶子,我们把这样的模型抽象出来,称作树。

我们对树的结构提出以下几点要求:

  1. 树有一个特殊的节点,也就是根节点,根节点没有前驱。
  2. 树的子树之间互不相交。
  3. 除了根节点外,每个节点只有一个前驱(父节点),可以有多个后继。

因此树是没有环的。

2. 树的相关知识点

  1. 结点的度:一个结点含有子树的个数称为该结点的度。
  2. 树的度:一棵树中,所有结点度的最大值称为树的度。
  3. 叶子结点或终端结点:度为0的结点称为叶结点。
  4. 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点。
  5. 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点。
  6. 根结点:一棵树中,没有双亲结点的结点。
  7. 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推。
  8. 树的高度或深度:树中结点的最大层次。

3. 树的表示方法

由于树是非线性的,因此存储表示会更麻烦。我们可以用双亲表示法,孩子表示法,孩子双亲表示法,孩子兄弟表示法等方式进行表示,其中孩子兄弟表示法最为常用:

java 复制代码
class Node {
    int value; // 树中存储的数据
    Node firstChild; // 第一个孩子引用
    Node nextBrother; // 下一个兄弟引用
}

我们实现二叉树的时候会简化表示的方式,这些表示方式仅作了解即可。

二、二叉树

1. 二叉树的概念

二叉树是树的一种特殊形式,要求树的度小于等于2 ,并且二叉树的树有左右之分,次序不能颠倒,因此二叉树是有序树 。如图:

有两种特殊的二叉树需要特别介绍:

  1. 满二叉树:每一层的节点数都达到最大值,例如:一个满二叉树的层数是k,节点总数是2^k - 1。

  2. 完全二叉树:叶子结点只能出现在最下层和次下层,最后一层的叶子结点在左边连续,倒数第二层的叶子结点在右边连续,我们称为完全二叉树。(相当于是从左到右填充节点,一层填满了填下一层)

    满二叉树是特殊的完全二叉树。

2. 二叉树的创建

同样树也有对应的顺序存储和链式存储的两种方式,本文只介绍链式存储。

节点

我们采用孩子表示法来表示二叉树,跟链表类似,我们依旧采用内部类的方式定义二叉树的节点:

java 复制代码
// 孩子表示法
class TreeNode {
    int val; // 数据域
    TreeNode left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
    TreeNode right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
    TreeNode(int val) {
        this.val = val;
    }
}
创建一个树

我们定义一个 createTree() 方法,用于创建简单的树。

java 复制代码
public TreeNode createTree() {
        TreeNode a = new TreeNode(1);
        TreeNode b = new TreeNode(2);
        TreeNode c = new TreeNode(3);
        TreeNode d = new TreeNode(4);
        TreeNode e = new TreeNode(5);
        TreeNode f = new TreeNode(6);

        a.left = b;
        a.right = c;
        b.left = d;
        c.left = e;
        c.right = f;

        return a;
    }

创造的二叉树如图:

3. 树的大小和深度

size()

我们采用两种方法进行计算:

  1. 内部递归输出
java 复制代码
public int size(TreeNode root) {
        int size = 0;
        if (root == null) {
            return 0;
        }
        return size(root.left) + size(root.right) + 1;
    }
  1. 成员变量输出
java 复制代码
int size = 0;
    public void size2(TreeNode root) {
        if (root == null) {
            return;
        }
        size++;
        size2(root.left);
        size2(root.right);
    }

或者每次加入元素的时候 size++。但是因为不涉及添加元素方法的书写,因此不细说。

getHeight()

思路是树的高度是左树和右树深度最大值加上1,需要用到 Math 类的 max 方法。

java 复制代码
public int getHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int height = 0;
        return Math.max(getHeight(root.left), getHeight(root.right)) + 1;
    }

4. 二叉树的遍历

树的遍历分为四种:前序遍历,中序遍历,后序遍历,层序遍历,遵循的顺序分别是:根-左树-右树,左树-根-右树,左树-右树-根,层内从左到右。前中后是指根在左树和右树的位置。

例如在上面给出的图中,四种遍历的结果如下:

1 2 4 3 5 6

4 2 1 5 3 6

4 2 5 6 3 1

1 2 3 4 5 6

递归实现

四种方法的递归实现如下:

1.前序遍历

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

2.中序遍历

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

3.后序遍历

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

4.层序遍历

为了实现层序遍历我们采用了辅助方法进行运算。

java 复制代码
public void levelOrder(TreeNode root, int k) {
        if (root == null) {
            return;
        }
        if (k == 1) {
            System.out.print(root.val + " ");
        }
        levelOrder(root.left, k - 1);
        levelOrder(root.right, k - 1);
    }

    public void levelOrder(TreeNode root) {
        int height = getHeight(root);
        for (int i = 1; i <= height; i++) {
            levelOrder(root, i);
        }
    }
非递归实现

借助别的数据结构我们可以实现树的非递归遍历。

对于层序遍历,我们采用队列进行存储。具体如下:

java 复制代码
public void levelOrder1(TreeNode root) {
        if (root == null) {
            return;
        }
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        while (!q.isEmpty()) {
            for (int i = 0; i < q.size(); i++) {
                TreeNode node = q.poll();
                System.out.println(node.val);
                if (node.left != null) {
                    q.offer(node.left);
                }
                if (node.right != null) {
                    q.offer(node.right);
                }
            }
        }

而前中后序遍历我们则是采用栈的数据结构。

前序遍历:

java 复制代码
    public void preOrder1(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                System.out.print(cur.val + " ");
                cur = cur.left;
            }
            cur = stack.pop();
            cur = cur.right;
        }
    }

中序遍历:

我们只需要把输出的时间从入栈改成出栈即可:

java 复制代码
public void inOrder1(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            cur = stack.pop();
            System.out.print(cur.val + " ");
            cur = cur.right;
        }
    }

后序遍历:

会更加麻烦一点,特别是在右节点的判定上。

因此我们引入了 topprev 变量,用于判断右树是否已经遍历完毕。

java 复制代码
    public void postOrder1(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode prev = null;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode top = stack.peek();
            if (top.right == null || prev == top.right) {
                System.out.print(top.val + " ");
                prev = top;
                stack.pop();
            } else {
                cur = top.right;
            }
        }
    }

下一篇博客讲一些经典的二叉树练习题。

相关推荐
大G的笔记本2 小时前
Java WebSocket客户端--java.net.http.HttpClient
java·websocket·.net
我是李龙2 小时前
第二十一章 项目启动与治理架构:从招标到甲乙方协作机制的建立
java·架构·devops
我不是懒洋洋2 小时前
【经典题目】链表OJ(轮转数组、返回倒数第k个节点、链表的回文结构)
c语言·开发语言·数据结构·算法·链表·visual studio
rannn_1112 小时前
【Redis|高级篇2】多级缓存|JVM进程缓存、Lua语法、多级缓存实现(OpenResty)、缓存同步(Canal)
java·redis·分布式·后端·缓存·lua·openresty
Lyyaoo.2 小时前
【JAVA基础面经】CAS 与 ABA
java·开发语言
Allen_LVyingbo2 小时前
《狄拉克符号法50讲》习题与解析(上)
开发语言·人工智能·python·数学建模·量子计算
励志的小陈2 小时前
数据结构--堆(C语言实现)
android·c语言·数据结构
AC赳赳老秦2 小时前
OpenClaw对接百度指数:关键词热度分析,精准定位博客创作方向
java·python·算法·百度·dubbo·deepseek·openclaw
charlie1145141912 小时前
通用GUI编程技术——图形渲染实战(三十)——Direct2D几何体系统:从路径到命中测试
开发语言·c++·windows·信息可视化·c·图形渲染·win32