浅谈二叉树,平衡二叉树,红黑树以及java简单实现

浅谈二叉树,平衡二叉树,红黑树以及java简单实现

二叉树的相关介绍

二叉树(Binary Tree)

  • 二叉树是一种每个节点最多只有两个子节点的树结构。
  • 每个节点最多有两个子节点,称为左子节点和右子节点。
  • 二叉树的节点可以为空(null)。
  • 二叉树常用于实现搜索算法和排序算法,例如二叉搜索树和堆等数据结构。

二叉树 的第 i 层至多拥有 2^(i-1) 个节点,深度为 k 的二叉树至多总共有 2^(k+1)-1 个节点(满二叉树的情况),至少有 2^(k) 个节点
在这里插入图片描述

基于二叉查找树的这种特点,在查找某个节点的时候,可以采取类似于二分查找的思想,快速找到某个节点。n 个节点的二叉查找树,正常的情况下,查找的时间复杂度为 O(logN)。之所以说是正常情况下,是因为二叉查找树有可能出现一种极端的情况
在这里插入图片描述

这种情况也是满足二叉查找树的条件,然而,此时的二叉查找树已经近似退化为一条链表,这样的二叉查找树的查找时间复杂度顿时变成了 O(n)。由此必须防止这种情况发生,为了解决这个问题,于是引申出了平衡二叉树

优点:有序 缺点:极端条件下会退化成链表,降低查找效率

实际应用场景:

  • 表达式树:用于表示和计算数学表达式,便于进行求值和运算。
  • 二叉搜索树(Binary Search Tree):用于快速查找和插入数据,常见于数据库索引和字典实现等。
  • 哈夫曼树(Huffman Tree):用于数据压缩,通过构建最优二叉树来实现数据的编码和解码。

平衡二叉树(Balanced Binary Tree)

  • 平衡二叉树是一种二叉树,其左右子树的高度差不超过某个固定的值(如1)。
  • 平衡二叉树的目的是保持树的高度平衡,以提高搜索、插入和删除等操作的效率。
  • 平衡二叉树的常见实现包括AVL树和红黑树。

AVL树也规定了左结点小于根节点,右结点大于根节点。并且还规定了左子树和右子树的高度差不得超过1。这样保证了它不会成为线性的链表。 AVL树的查找稳定,查找、插入、删除的时间复杂度都为O(logN) 但是由于要维持自身的平衡,所以进行插入和删除结点操作的时候,需要对结点进行频繁的旋转
在这里插入图片描述

优点:有序,解决了BST会退化成线性结构的问题 缺点:进行插入和删除结点操作的时候,需要对结点进行频繁的旋转

实际应用场景:

  • AVL树:一种自平衡的二叉搜索树,常用于数据库索引、集合类的实现等,保证插入和删除操作的时间复杂度为O(log N)。
  • Treap树:结合了二叉搜索树和堆的特性,常用于排序和动态顺序统计等场景。
  • 伸展树(Splay Tree):通过频繁访问的节点放到树的根节点来提高访问效率,常用于缓存系统和网络路由器等。

红黑树(Red-Black Tree)

由来

虽然平衡树解决了二叉查找树退化为近似链表的缺点,能够把查找时间控制在 O(logn),不过却不是最佳的,因为平衡树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在是太严了,导致每次进行插入/删除节点的时候,几乎都会破坏平衡树的第二个规则,进而都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树

简介

红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是 red 或 black。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍。它是一种弱平衡二叉树 (由于是若平衡,可以推出,相同的节点情况下,AVL 树的高度低于红黑树),相对于要求严格的 AVL 树来说,它的旋转次数变少,所以对于搜索、插入、删除操作多的情况下,我们就用红黑树。

特性
  • 红黑树是一种自平衡的二叉查找树。
  • 每个节点都有一个颜色属性,红色或黑色。
  • 红黑树具有以下特性:
    • 根节点是黑色的。
    • 所有叶子节点(空节点)都是黑色的。
    • 如果一个节点是红色的,则其两个子节点都是黑色的。
    • 任意节点到其每个叶子节点的路径上,黑色节点的数量是相同的。
  • 红黑树的平衡性质保证了树的高度近似于 log(N),其中 N 是节点数量。

在这里插入图片描述

优点

  1. 平衡性: 红黑树通过保持特定的平衡性质,能够保持树的高度相对较低,从而提供快速的插入、删除和搜索操作。其时间复杂度为 O(log N),其中 N 是树中节点的数量。
  2. 高效的查找: 红黑树的结构允许高效的查找操作,比线性数据结构(如数组)更快速,且具有稳定的查找性能。
  3. 有序性: 红黑树中的节点按照键值的顺序排列,使得它非常适用于需要有序遍历的场景,例如范围查询等。
  4. 广泛应用: 红黑树被广泛应用于各种数据结构和算法实现中,如集合类、关联容器、内存管理等。

缺点

  1. 相对复杂: 相比于普通的二叉搜索树,红黑树的实现和维护较为复杂,需要处理各种旋转和颜色变换等操作,这可能会增加代码的复杂性。
  2. 内存占用: 相比于其他树结构,红黑树需要额外的存储空间来存储节点的颜色信息,这可能导致额外的内存消耗。
  3. 插入和删除操作的调整: 红黑树的插入和删除操作需要进行节点的旋转和颜色调整,这可能导致算法的实现相对较复杂,且一些情况下需要进行多次调整。
  4. 不适合频繁修改: 如果需要频繁地插入和删除节点,红黑树的维护操作可能会带来较大的开销,因为平衡性的维护可能需要进行多次旋转和颜色调整

实际应用场景:

  • STL(C++标准模板库)中的map和set的实现:红黑树常用于实现关联容器,提供高效的查找、插入和删除操作。
  • Linux内核中的进程调度器(Completely Fair Scheduler):使用红黑树来管理进程的优先级和调度顺序。
  • Java的TreeMap和TreeSet:底层采用红黑树实现,提供有序的键值存储和高效的查找。
简单代码实现
public class RedBlackTree<T extends Comparable<T>> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private Node<T> root;

    private class Node<T> {
        T value;            // 节点存储的值
        Node<T> left;       // 左子节点
        Node<T> right;      // 右子节点
        boolean color;      // 节点颜色(红色或黑色)

        Node(T value, boolean color) {
            this.value = value;
            this.color = color;
        }
    }

    // 判断节点的颜色
    private boolean isRed(Node<T> node) {
        if (node == null) {
            return false;
        }
        return node.color == RED;
    }

    /**
     * 执行左旋转操作,将指定节点的右子节点旋转为其父节点,返回旋转后的根节点。
     *
     * @param h 要执行左旋转的节点
     * @return 旋转后的根节点
     */
    private Node<T> rotateLeft(Node<T> h) {
        Node<T> x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = h.color;
        h.color = RED;
        return x;
    }

    /**
     * 执行右旋转操作,将指定节点的左子节点旋转为其父节点,返回旋转后的根节点。
     *
     * @param h 要执行右旋转的节点
     * @return 旋转后的根节点
     */
    private Node<T> rotateRight(Node<T> h) {
        Node<T> x = h.left;
        h.left = x.right;
        x.right = h;
        x.color = h.color;
        h.color = RED;
        return x;
    }

    /**
     * 执行颜色翻转操作,将指定节点及其左右子节点的颜色进行交换。
     * 要求指定节点的左右子节点必须存在且颜色均为黑色。
     *
     * @param h 指定节点
     */
    private void flipColors(Node<T> h) {
        h.color = RED;
        h.left.color = BLACK;
        h.right.color = BLACK;
    }


    /**
     * 插入节点
     *
     * @param value 要插入的节点值
     */
    public void insert(T value) {
        root = insert(root, value);
        root.color = BLACK;
    }

    private Node<T> insert(Node<T> node, T value) {
        if (node == null) {
            // 如果当前节点为空,说明已到达插入位置,创建新节点并返回
            return new Node<>(value, RED);
        }

        int cmp = value.compareTo(node.value);
        if (cmp < 0) {
            // 如果要插入的值小于当前节点值,向左子树递归插入
            node.left = insert(node.left, value);
        } else if (cmp > 0) {
            // 如果要插入的值大于当前节点值,向右子树递归插入
            node.right = insert(node.right, value);
        } else {
            // 如果要插入的值等于当前节点值,更新当前节点的值
            node.value = value;
        }

        // 红黑树的插入修复操作
        if (isRed(node.right) && !isRed(node.left)) {
            // 如果当前节点的右子节点是红色而左子节点是黑色,进行左旋转
            node = rotateLeft(node);
        }
        if (isRed(node.left) && isRed(node.left.left)) {
            // 如果当前节点的左子节点是红色且其左子节点也是红色,进行右旋转
            node = rotateRight(node);
        }
        if (isRed(node.left) && isRed(node.right)) {
            // 如果当前节点的左子节点和右子节点都是红色,进行颜色翻转
            flipColors(node);
        }

        return node;
    }


    /**
     * 删除节点
     *
     * @param value 要删除的节点值
     */
    public void delete(T value) {
        if (!contains(value)) {
            return;
        }
        if (!isRed(root.left) && !isRed(root.right)) {
            root.color = RED;
        }
        root = delete(root, value);
        if (root != null) {
            root.color = BLACK;
        }
    }


    /**
     * 在以指定节点为根的子树中删除指定值的节点,并返回删除后的根节点。
     *
     * @param node  子树的根节点
     * @param value 要删除的节点值
     * @return 删除后的根节点
     */
    private Node<T> delete(Node<T> node, T value) {
        if (value.compareTo(node.value) < 0) {
            if (!isRed(node.left) && !isRed(node.left.left)) {
                // 如果当前节点的左子节点和左子节点的左子节点都是黑色,进行移动操作
                node = moveRedLeft(node);
            }
            // 向左子树递归删除
            node.left = delete(node.left, value);
        } else {
            if (isRed(node.left)) {
                // 如果当前节点的左子节点是红色,进行右旋转
                node = rotateRight(node);
            }
            if (value.compareTo(node.value) == 0 && node.right == null) {
                // 如果找到了要删除的节点,并且当前节点的右子节点为空,直接删除
                return null;
            }
            if (!isRed(node.right) && !isRed(node.right.left)) {
                // 如果当前节点的右子节点和右子节点的左子节点都是黑色,进行移动操作
                node = moveRedRight(node);
            }
            if (value.compareTo(node.value) == 0) {
                // 找到了要删除的节点
                Node<T> min = findMin(node.right);
                node.value = min.value;
                node.right = deleteMin(node.right);
            } else {
                // 向右子树递归删除
                node.right = delete(node.right, value);
            }
        }
        return balance(node);
    }


    /**
     * 对当前节点进行红色翻转,并进行左旋转操作。
     * 如果当前节点的右子节点的左子节点是红色,则进行相应的右旋转和左旋转操作后,进行颜色翻转。
     *
     * @param node 当前节点
     * @return 旋转后的节点
     */
    private Node<T> moveRedLeft(Node<T> node) {
        flipColors(node); // 红色翻转
        if (isRed(node.right.left)) {
            node.right = rotateRight(node.right); // 右旋转
            node = rotateLeft(node); // 左旋转
            flipColors(node); // 颜色翻转
        }
        return node;
    }


    /**
     * 对当前节点进行红色翻转,并进行右旋转操作。
     * 如果当前节点的左子节点的左子节点是红色,则进行右旋转操作后,进行颜色翻转。
     *
     * @param node 当前节点
     * @return 旋转后的节点
     */
    private Node<T> moveRedRight(Node<T> node) {
        flipColors(node); // 红色翻转
        if (isRed(node.left.left)) {
            node = rotateRight(node); // 右旋转
            flipColors(node); // 颜色翻转
        }
        return node;
    }


    /**
     * 删除以当前节点为根的子树中的最小节点,并返回删除节点后的根节点。
     *
     * @param node 当前节点
     * @return 删除节点后的根节点
     */
    private Node<T> deleteMin(Node<T> node) {
        if (node.left == null) {
            // 如果左子节点为空,说明已经到达最小节点,返回 null
            return null;
        }
        if (!isRed(node.left) && !isRed(node.left.left)) {
            // 如果当前节点的左子节点和左子节点的左子节点都是黑色,进行移动操作
            node = moveRedLeft(node);
        }
        // 递归删除左子树中的最小节点
        node.left = deleteMin(node.left);
        return balance(node);
    }

    /**
     * 查找以当前节点为根的子树中的最小节点。
     *
     * @param node 当前节点
     * @return 最小节点
     */
    private Node<T> findMin(Node<T> node) {
        if (node.left == null) {
            // 如果左子节点为空,说明已经到达最小节点,返回当前节点
            return node;
        }
        // 递归查找左子树中的最小节点
        return findMin(node.left);
    }

    /**
     * 对当前节点进行颜色调整,保持红黑树的平衡性。
     *
     * @param node 当前节点
     * @return 调整后的节点
     */
    private Node<T> balance(Node<T> node) {
        if (isRed(node.right)) {
            // 如果当前节点的右子节点是红色,进行左旋转
            node = rotateLeft(node);
        }
        if (isRed(node.left) && isRed(node.left.left)) {
            // 如果当前节点的左子节点和左子节点的左子节点都是红色,进行右旋转
            node = rotateRight(node);
        }
        if (isRed(node.left) && isRed(node.right)) {
            // 如果当前节点的左子节点和右子节点都是红色,进行颜色翻转
            flipColors(node);
        }
        return node;
    }


    /**
     * 查找节点
     *
     * @param value 要查找的节点值
     * @return 如果节点存在返回 true,否则返回 false
     */
    public boolean contains(T value) {
        return contains(root, value);
    }

    private boolean contains(Node<T> node, T value) {
        if (node == null) {
            return false;
        }

        int cmp = value.compareTo(node.value);
        if (cmp < 0) {
            return contains(node.left, value);
        } else if (cmp > 0) {
            return contains(node.right, value);
        } else {
            return true;
        }
    }
}

二叉树的存储

链式存储结构(Linked Representation):

  • 在链式存储结构中,每个节点由一个包含数据和指向左右子节点的指针(引用)的对象(或结构体)表示。
  • 每个节点对象包含三个成员变量:数据、左子节点指针和右子节点指针。
  • 通过指针的连接,可以形成一个动态的树结构,每个节点可以灵活地连接任意数量的子节点。
  • 链式存储结构易于实现和操作,适用于任意形状和大小的二叉树。

在这里插入图片描述

简单代码实现
树节点对象
public class BinaryTreeNode<T> {

    T val;
    BinaryTreeNode<T> left;
    BinaryTreeNode<T> right;

    public BinaryTreeNode(T val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}
树结构实现
public class BinaryTree<T extends Comparable<T>> {

    private BinaryTreeNode<T> root;

    public BinaryTree() {
        root = null;
    }

    /**
     * 插入节点
     */
    public void insert(T val) {
        root = insertHelper(root, val);
    }

    /**
     * 递归地插入节点到二叉树中
     *
     * @param node 当前节点
     * @param val  要插入的值
     * @return 插入节点后的二叉树的根节点
     */
    private BinaryTreeNode<T> insertHelper(BinaryTreeNode<T> node, T val) {
        if (node == null) {
            // 若当前节点为空,创建一个新节点并将其作为叶子节点插入到二叉树中
            return new BinaryTreeNode<>(val);
        }

        if (val.compareTo(node.val) < 0) {
            // 若插入值小于当前节点值,则插入到左子树
            node.left = insertHelper(node.left, val);
        } else if (val.compareTo(node.val) > 0) {
            // 若插入值大于当前节点值,则插入到右子树
            node.right = insertHelper(node.right, val);
        }

        return node;
    }

    /**
     * 删除节点
     */
    public void delete(T val) {
        root = deleteHelper(root, val);
    }


    /**
     * 递归地删除节点
     *
     * @param node 当前节点
     * @param val  要删除的值
     * @return 删除节点后的二叉树的根节点
     */
    private BinaryTreeNode<T> deleteHelper(BinaryTreeNode<T> node, T val) {
        if (node == null) {
            return null;
        }

        if (val.compareTo(node.val) < 0) {
            // 若要删除的值小于当前节点值,则继续在左子树中删除
            node.left = deleteHelper(node.left, val);
        } else if (val.compareTo(node.val) > 0) {
            // 若要删除的值大于当前节点值,则继续在右子树中删除
            node.right = deleteHelper(node.right, val);
        } else {
            // 找到目标节点
            if (node.left == null && node.right == null) {
                // 目标节点为叶子节点
                return null;
            } else if (node.left == null) {
                // 目标节点只有右子节点
                return node.right;
            } else if (node.right == null) {
                // 目标节点只有左子节点
                return node.left;
            } else {
                // 目标节点有左右子节点
                BinaryTreeNode<T> successor = findMin(node.right);
                node.val = successor.val;
                node.right = deleteHelper(node.right, successor.val);
            }
        }

        return node;
    }

    /**
     * 查找二叉树中的最小值节点
     *
     * @param node 当前节点
     * @return 二叉树中的最小值节点
     */
    private BinaryTreeNode<T> findMin(BinaryTreeNode<T> node) {
        while (node.left != null) {
            node = node.left;
        }
        return node;
    }

    /**
     * 查找节点
     */
    public BinaryTreeNode<T> search(T val) {
        return searchHelper(root, val);
    }


    /**
     * 递归地查找节点
     *
     * @param node 当前节点
     * @param val  要查找的值
     * @return 找到的节点或 null(若节点不存在)
     */
    private BinaryTreeNode<T> searchHelper(BinaryTreeNode<T> node, T val) {
        if (node == null || val.compareTo(node.val) == 0) {
            return node;
        }

        if (val.compareTo(node.val) < 0) {
            return searchHelper(node.left, val);
        } else {
            return searchHelper(node.right, val);
        }
    }
}
测试代码
 BinaryTree<Integer> binaryTree = new BinaryTree<>();

        // 插入节点
        binaryTree.insert(5);
        binaryTree.insert(3);
        binaryTree.insert(7);
        binaryTree.insert(2);
        binaryTree.insert(4);

        // 查找节点
        BinaryTreeNode<Integer> node = binaryTree.search(3);
        if (node != null) {
            System.out.println("Found node with value 3");
        } else {
            System.out.println("Node with value 3 not found");
        }

        // 删除节点
        binaryTree.delete(5);
        node = binaryTree.search(5);
        if (node != null) {
            System.out.println("Found node with value 5");
        } else {
            System.out.println("Node with value 5 not found");
        }

顺序存储结构(Array Representation):

  • 在顺序存储结构中,使用一个数组来存储二叉树的节点,按照一定的规律将节点在数组中进行排列。
  • 通常使用数组的索引来表示节点之间的关系。对于一个节点在数组中的索引为 i,其左子节点的索引为 2i+1,右子节点的索引为 2i+2
  • 顺序存储结构将二叉树的节点按照层次顺序依次存储在数组中,从根节点开始,依次存储每一层的节点。
  • 顺序存储结构节省了指针的存储空间,对于完全二叉树来说,利用数组的连续内存可以更高效地进行访问和遍历。
  • 顺序存储结构适用于完全二叉树或近似完全二叉树,对于一般的二叉树,可能会浪费部分数组空间。

完全二叉树存储

在这里插入图片描述

非完全二叉树存储 (在数组中就会出现空隙,导致内存利用率降低)

在这里插入图片描述

简单代码实现
public class ArrayBinaryTree<T> {

    private T[] array;
    private int size;

    /**
     * 构造函数,初始化数组和大小
     *
     * @param capacity 数组容量
     */
    public ArrayBinaryTree(int capacity) {
        array = (T[]) new Object[capacity];
        size = 0;
    }

    /**
     * 插入节点
     *
     * @param data 要插入的节点值
     */
    public void insert(T data) {
        if (size == array.length) {
            System.out.println("Binary tree is full, cannot insert new node.");
            return;
        }

        array[size] = data;
        size++;
    }

    /**
     * 删除节点
     *
     * @param data 要删除的节点值
     */
    public void delete(T data) {
        int index = search(data);
        if (index == -1) {
            System.out.println("Node with value " + data + " not found.");
            return;
        }

        // 将最后一个节点的值复制到要删除的节点位置,并将最后一个节点置为空
        array[index] = array[size - 1];
        array[size - 1] = null;
        size--;
    }

    /**
     * 查找节点
     *
     * @param data 要查找的节点值
     * @return 节点值在数组中的索引,若未找到则返回 -1
     */
    public int search(T data) {
        for (int i = 0; i < size; i++) {
            if (array[i].equals(data)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 修改节点值
     *
     * @param oldData 要修改的节点旧值
     * @param newData 要修改的节点新值
     */
    public void update(T oldData, T newData) {
        int index = search(oldData);
        if (index == -1) {
            System.out.println("Node with value " + oldData + " not found.");
            return;
        }

        array[index] = newData;
    }

    /**
     * 输出二叉树的内容
     */
    public void print() {
        for (int i = 0; i < size; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}

对比

存储方式优点缺点适用场景
链式存储- 灵活插入和删除节点
- 不受固定容量限制
- 需要额外的指针空间
- 访问元素需要遍历链表
- 需要频繁插入和删除节点的情况
- 不知道数据量大小的情况
顺序存储- 直接访问元素,效率高- 需要预先分配固定容量
- 插入和删除开销较大
- 数据量已知且固定的情况
- 需要高效访问元素的情况

链式存储适用于需要频繁插入和删除节点的情况,由于链式存储不需要连续的内存空间,可以灵活地进行节点的插入和删除操作。链式存储的缺点是需要额外的指针空间,并且访问元素需要遍历链表。

顺序存储适用于数据量已知且固定的情况,由于顺序存储使用连续的内存空间存储元素,可以直接访问元素,因此访问效率较高。顺序存储的缺点是需要预先分配固定容量,如果数据量超过容量限制,则需要重新分配空间,并且插入和删除元素的开销较大。

根据具体的需求和场景,可以选择适合的存储方式。链式存储适合需要频繁插入和删除节点的情况,而顺序存储适合数据量已知且固定,需要高效访问元素的情况。

二叉树遍历

前序遍历(Preorder):
  • 介绍:前序遍历是指先访问根节点,然后按照先左后右的顺序遍历左右子树。
  • 使用场景:前序遍历可以用于打印表达式、构造二叉树、复制二叉树等。
  • 优点:前序遍历是最自然、最直观的遍历方式,容易理解和实现。
  • 缺点:前序遍历的输出顺序可能不符合某些特定需求。

在这里插入图片描述

  • Java 代码实现:
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    public TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class PreorderTraversal {
    public void preorder(TreeNode root) {
        if (root == null) {
            return;
        }

        System.out.print(root.val + " ");  // 先访问根节点
        preorder(root.left);  // 遍历左子树
        preorder(root.right);  // 遍历右子树
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);

        PreorderTraversal traversal = new PreorderTraversal();
        System.out.println("Preorder traversal:");
        traversal.preorder(root);
    }
}
中序遍历(Inorder):
  • 介绍:中序遍历是指按照先左后根再右的顺序遍历左右子树,即从小到大顺序输出元素。
  • 使用场景:中序遍历可以用于二叉搜索树的排序、表达式求值、二叉树的递增序列等。
  • 优点:中序遍历可以按照从小到大的顺序输出有序二叉树的节点值。
  • 缺点:对于一些特定需求,中序遍历的输出顺序可能不符合要求。

在这里插入图片描述

  • Java 代码实现:
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    public TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class InorderTraversal {
    public void inorder(TreeNode root) {
        if (root == null) {
            return;
        }

        inorder(root.left);  // 遍历左子树
        System.out.print(root.val + " ");  // 访问根节点
        inorder(root.right);  // 遍历右子树
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(4);
        root.left = new TreeNode(2);
        root.right = new TreeNode(6);
        root.left.left = new TreeNode(1);
        root.left.right = new TreeNode(3);
        root.right.left = new TreeNode(5);
        root.right.right = new TreeNode(7);

        InorderTraversal traversal = new InorderTraversal();
        System.out.println("Inorder traversal:");
        traversal.inorder(root);
    }
}
后序遍历(Postorder):
  • 介绍:后序遍历是指按照先左后右再根的顺序遍历左右子树。
  • 使用场景:后序遍历可以用于内存回收、文件系统的目录结构等。
  • 优点:后序遍历在释放内存或处理文件系统目录结构时,能够确保子节点的操作已经完成。
  • 缺点:后序遍历的输出顺序可能不符合某些特定需求。

在这里插入图片描述

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    public TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class PostorderTraversal {
    public void postorder(TreeNode root) {
        if (root == null) {
            return;
        }

        postorder(root.left);  // 遍历左子树
        postorder(root.right);  // 遍历右子树
        System.out.print(root.val + " ");  // 访问根节点
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(4);
        root.left = new TreeNode(2);
        root.right = new TreeNode(6);
        root.left.left = new TreeNode(1);
        root.left.right = new TreeNode(3);
        root.right.left = new TreeNode(5);
        root.right.right = new TreeNode(7);

        PostorderTraversal traversal = new PostorderTraversal();
        System.out.println("Postorder traversal:");
        traversal.postorder(root);
    }
}

t val) {
this.val = val;
this.left = null;
this.right = null;
}
}

public class PostorderTraversal {
public void postorder(TreeNode root) {
if (root == null) {
return;
}

    postorder(root.left);  // 遍历左子树
    postorder(root.right);  // 遍历右子树
    System.out.print(root.val + " ");  // 访问根节点
}

public static void main(String[] args) {
    TreeNode root = new TreeNode(4);
    root.left = new TreeNode(2);
    root.right = new TreeNode(6);
    root.left.left = new TreeNode(1);
    root.left.right = new TreeNode(3);
    root.right.left = new TreeNode(5);
    root.right.right = new TreeNode(7);

    PostorderTraversal traversal = new PostorderTraversal();
    System.out.println("Postorder traversal:");
    traversal.postorder(root);
}

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值