玩转二叉树

树是计算机编程当中很基础的一种数据结构,二叉树则是树中最基本的一种。虽然很多程序员在日常工作中未必会直接用到二叉树,但是对于这样一种基础的数据结构,熟练掌握其基本概念和操作,有助于关键场合不露怯。毕竟,所有的玩儿法都是由最基本最核心的理念衍生而成。

基本概念

二叉树,顾名思义,从根结点开始,每个结点可以分出最多两个子结点,没有子结点的结点叫叶子。

形态

满二叉树

“满”即是“饱满”:一个从根到叶子,每一个非叶子结点都分出两个子结点,看上去是那么的饱满。其总结点个数计算公式为:$2k-1$,$k$为深度;第$n$层结点个数计算公式为:$2$。

完全二叉树

不饱满也不要紧,只要缺的都是右叶子而不缺左叶子。其深度计算公式为:$\lfloor log_2k\rfloor+1$。

二叉搜索树/红黑树

左子结点值小于父结点值,父结点值小于右子结点值,且左子树中所有结点值均小于根结点值,根结点值小于右子树中任一结点值。

基本操作

定义

public class TreeNode {
    private int value;
    private TreeNode leftChildNode;
    private TreeNode rightChildNode;

    TreeNode() {
    }

    TreeNode(int value) {
        this.value = value;
    }

    TreeNode(int value, TreeNode leftChildNode, TreeNode rightChildNode) {
        this.value = value;
        this.leftChildNode = leftChildNode;
        this.rightChildNode = rightChildNode;
    }
}

遍历

深度优先

深度遍历二叉树分为前序、中序和后序遍历,一般采用递归的实现形式比较简洁。无论哪种遍历,左结点一定是在右结点之前遍历,而所谓的前、中、后,则是中间结点在什么时机处理的区别。因此:

  • 前序遍历 = → 左 → 右
  • 中序遍历 = 左 → → 右
  • 后序遍历 = 左 → 右 →
public void traverseDepthFirst(TreeNode node) {
    if (node == null) {
        return;
    }

    System.out.println(node.value); // Preorder.
    traverseDepthFirst(node.leftChildNode);
    // System.out.println(node.value); // Inorder.
    traverseDepthFirst(node.rightChildNode);
    // System.out.println(node.value); // Postorder.
}

宽度优先

层序遍历二叉树一般可以用LinkedList或类似的数据类结合迭代来实现:

public void traverseBreadthFirst(TreeNode rootNode) {
    LinkedList<TreeNode> linkedList = new LinkedList<>();

    if (rootNode == null) {
        return;
    }

    linkedList.offer(rootNode);

    while (!linkedList.isEmpty()) {
        int size = linkedList.size();

        for (int index = 0; index < size; index++) {
            TreeNode node = linkedList.pop();

            if (node == null) {
                continue;
            }

            System.out.println(node.value);

            linkedList.offer(node.leftChildNode);
            linkedList.offer(node.rightChildNode);
        }
    }
}

聊完遍历来看看构建吧。可以通过两个数组来构建一个二叉树,这两个数组分别对应的是二叉树的前序及中序遍历,或者中序与后序遍历。下面这段代码就是通过递归的方式,用前序和中序遍历数组构建出一个二叉树。而利用中序和后序遍历构建与这种方式雷同,区别在于:前序数组的第一个元素代表父结点,后续数组中对应的是最后一个元素。

public TreeNode buildTree(int[] preorder, int[] inorder) {
    if (preorder == null || preorder.length == 0 || inorder == null || inorder.length == 0
            || preorder.length != inorder.length) {
        return null;
    }

    int rootValue = preorder[0];
    TreeNode root = new TreeNode(rootValue);

    if (preorder.length == 1) {
        return root;
    }

    int delimiterIndex = 0;

    for (int number : inorder) {
        if (number == rootValue) {
            break;
        }

        delimiterIndex++;
    }

    int[] leftInorder = Arrays.copyOfRange(inorder, 0, delimiterIndex);
    int[] rightInorder = Arrays.copyOfRange(inorder, delimiterIndex + 1, inorder.length);
    int[] leftPreorder = Arrays.copyOfRange(preorder, 1, delimiterIndex + 1);
    int[] rightPreorder = Arrays.copyOfRange(preorder, delimiterIndex + 1, preorder.length);

    root.leftChildNode = buildTree(leftPreorder, leftInorder);
    root.rightChildNode = buildTree(rightPreorder, rightInorder);

    return root;
}

二叉树最核心的概念和操作基本就是这些,至于其它诸如合并、翻转、搜索、验证、最大……之类的问题,无非就是在遍历或构建的不同阶段实现不同的逻辑需求。所以,与其试图记住所有二叉树问题的答案,倒不如回过头来,找到二叉树概念的根,彻彻底底地吃透,从而任何问题都可以自己推导出解来。

posted @ 2021-12-11 14:57  麦机长  阅读(162)  评论(0编辑  收藏  举报