数据结构-树

本文详细探讨了树的基本概念,如子树、结点度、二叉树特性、满二叉树与完全二叉树的区别。重点介绍了二叉树的数组和队列实现,以及前序、中序、后序和层次遍历算法。通过实例演示了Java如何在二叉树上进行这些遍历操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树的概念

在这里插入图片描述

子树

当结点数>1时,其余结点分为互不相交的集合称为子树。

结点的度

该结点的子树数量

树的度

树中各个结点度数的最大值。

叶子

所有度为0的结点,即子树为空的结点。

父结点

从根节点到该节点的路径上,离该结点最近的结点,称为该结点父节点

儿子结点

兄弟结点

同一个父结点的其他结点。

有序树

所有结点的儿子结点是有顺序的,否则为无序树

森林

n个互不相交的树的集合。

结点的高度

结点到叶子结点的最长路径。比如上图树1根节点1的高度为3。

结点的深度

根结点到该结点的边个数。比如上图树1结点4的深度为1。

结点的层数

结点的深度加1。

树的高度

根结点的高度。

二叉树

一种特殊的树形结构,每个节点最多有两颗子树,且子树有左右之分。

二叉树特性

  1. 每个节点最多有两个子树,子树有左右之分;
  2. 在二叉树的第n层,最多有n^(n-1)个结点;

满二叉树

除叶子结点外,每个结点都有左右两个子结点。

完全二叉树

完全二叉树的最底层结点都连续靠左排列,除最底层的结点外,其他结点必须都有左右子结点。
完全二叉树可以看作是满二叉树的一个子集。

在这里插入图片描述

完全二叉树特性

对一颗有n个结点的完全二叉树按照从第一层到最后一层,并且每层都按从左到右的次序进行排序。那么:

  1. 任何一个结点编号为i的结点来说,它的左子结点为:2 * i;右子结点为:2 * 1+1;
  2. 任何一个结点编号为i的结点来说,它的父节点的编号为:i / 2;

为什么树3是完全二叉树,而树4却不是呢?请看二叉树的保存

二叉树的实现

二叉树可以使用数组和链表来保存,先来看二叉树在数组中的保存方式。
根据完全二叉树的特性可知结点在数组中的存储方式如下图:

二叉树寻址
使用数组来保存二叉树,由于数组的内存地址是连续的,可以使用CPU预读机制,性能更高,而且也不需要额外开辟空间来维护指针。
但可以看出用数组存储非完全二叉树,会存在浪费空间的情况,这时可以使用队列来实现二叉树。

队列底层数据结构选择使用数组

所以完全二叉树可以用数组来实现,这也就是为什么还要分一个完全二叉树出来的原因。

数组实现

队列实现

二叉树的四种遍历方式

现在对下图中的树5进行遍历:

二叉树遍历

假如N代表根结点,L代表左子树,R代表右子树。

按照顺序遇到根节点就输出。

前序

NLR (根 -> 左 -> 右)

  1. 访问根结点;
  2. 前序遍历左子树;
  3. 前序遍历右子树;
    结果:ABCDEFGHK

中序

LNR (左 -> 根 -> 右)

  1. 中序遍历左子树;
  2. 访问根结点;
  3. 中序遍历右子树;
    结果:BDCAEHGKF

后序

LRN (左 -> 右 -> 根)

  1. 后序遍历左子树;
  2. 后序遍历右子树;
  3. 访问根结点;
    结果:DCBHKGFEA

层次

Mysql

Java实现遍历方式

前中后序三种遍历算法的时间复杂度为:O(n)

public class BinaryTree<E> {


    int resIndex = 0;
    private static final int DEFAULT_CAPACITY = 50;
    private Object[] res;
    private int capacity = DEFAULT_CAPACITY;

    public BinaryTree(int capacity) {
        if(capacity <= 0){
            this.capacity = DEFAULT_CAPACITY;
        }else{
            this.capacity = capacity;
        }
        this.res = new Object[this.capacity];
    }

    public BinaryTree() {
        this.res = new Object[capacity];
    }

    public int getResIndex() {
        return resIndex;
    }

    public int getCapacity() {
        return capacity;
    }

    public void setCapacity(int capacity) {
        this.capacity = capacity;
    }

    public E[] getRes() {
        E[] resData = (E[]) res;
        resIndex = 0;
        res = null;
        res = new Object[this.capacity];
        return resData;
    }

    /**
     * 前序遍历:NLR 根 -> 左 -> 右
     * @param rootTreeNode 根结点
     */
    public void prev(TreeNode<E> rootTreeNode){
        if(rootTreeNode == null){
            return;
        }
        //左子树
        TreeNode<E> leftTN = rootTreeNode.left;
        //右子树
        TreeNode<E> rightTN = rootTreeNode.right;
        /**
         * 顺序要按照NLR 根 -> 左 -> 右:
         * 1.根:根结点先加进去
         * 2.左:左子树不为空,就迭代
         * 3.右:右子树不为空,就迭代
         */
        //1.根:根结点先加进去
        res[resIndex++] = rootTreeNode;
        //2.左子树不为空,就迭代
        if(leftTN != null){
            prev(leftTN);
        }
        //3.右子树不为空,就迭代
        if(rightTN != null){
            prev(rightTN);
        }
    }

    /**
     * 中序遍历:LNR 左 -> 根 -> 右
     * @param rootTreeNode 根结点
     */
    public void mid(TreeNode<E> rootTreeNode){
        if(rootTreeNode == null){
            return;
        }
        //左子树
        TreeNode<E> leftTN = rootTreeNode.left;
        //右子树
        TreeNode<E> rightTN = rootTreeNode.right;
        /**
         * 顺序要按照LNR 左 -> 根 -> 右:
         * 1.左:左子树不为空,就迭代
         * 2.根:根结点加进去
         * 3.右:右子树不为空,就迭代
         */
        //1.左子树不为空,就迭代
        if(leftTN != null){
            mid(leftTN);
        }
        //2.根:根结点加进去
        res[resIndex++] = rootTreeNode;

        //3.右子树不为空,就迭代
        if(rightTN != null){
            mid(rightTN);
        }

    }

    /**
     * 后序遍历:LRN 左 -> 右 -> 根
     * @param rootTreeNode 根结点
     */
    public void last(TreeNode<E> rootTreeNode){
        if(rootTreeNode == null){
            return;
        }
        //左子树
        TreeNode<E> leftTN = rootTreeNode.left;
        //右子树
        TreeNode<E> rightTN = rootTreeNode.right;
        /**
         * 顺序要按照LNR 左 -> 右 -> 根:
         * 1.左:左子树不为空,就迭代
         * 2.右:右子树不为空,就迭代
         * 3.根:根结点加进去
         */
        //1.左子树不为空,就迭代
        if(leftTN != null){
            last(leftTN);
        }

        //2.右子树不为空,就迭代
        if(rightTN != null){
            last(rightTN);
        }

        //3.根:根结点加进去
        res[resIndex++] = rootTreeNode;

    }

    static class TreeNode<E> {
        E data;
        TreeNode<E> left;
        TreeNode<E> right;

        public TreeNode(E data, TreeNode<E> left, TreeNode<E> right) {
            this.data = data;
            this.left = left;
            this.right = right;
        }

        @Override
        public String toString() {
            return "TreeNode{" +
                    "data=" + data +"}";
        }
    }

    public static void main(String[] args) {
        //构建二叉树
        TreeNode<String> D = new TreeNode<>("D", null, null);
        TreeNode<String> C = new TreeNode<>("C", D, null);
        TreeNode<String> B = new TreeNode<>("B", null, C);
        TreeNode<String> H = new TreeNode<>("H", null, null);
        TreeNode<String> K = new TreeNode<>("K", null, null);
        TreeNode<String> G = new TreeNode<>("G", H, K);
        TreeNode<String> F = new TreeNode<>("F", G, null);
        TreeNode<String> E = new TreeNode<>("E", null, F);
        TreeNode<String> A = new TreeNode<>("A", B, E);

        BinaryTree<String> binaryTree = new BinaryTree<>(9);
        binaryTree.prev(A);
        System.out.println("前序遍历:" + Arrays.toString(binaryTree.getRes()));
        binaryTree.mid(A);
        System.out.println("中序遍历:" + Arrays.toString(binaryTree.getRes()));
        binaryTree.last(A);
        System.out.println("后序遍历:" + Arrays.toString(binaryTree.getRes()));

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cuidianjay

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

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

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

打赏作者

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

抵扣说明:

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

余额充值