二叉树数据结构全面解析与代码实现

37 阅读6分钟

一、树的基本概念与结构

1.1 树的定义与术语

树是一种非线性的数据结构,由n个节点组成的有层次关系的集合。

重要术语

  • 根节点:树的顶端节点(如图中的A)
  • 父节点/子节点:节点间的层级关系(A是B的父节点,B是A的子节点)
  • 叶子节点:没有子节点的节点(如H、I)
  • 节点的度:节点拥有的子节点个数
  • 树的深度:树的最大层级数

1.2 树的表示方法

在Java中,我们使用节点类来表示树:

java

class TreeNode {
    int val;           // 节点存储的数据
    TreeNode left;     // 左子节点引用
    TreeNode right;    // 右子节点引用
    
    public TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

二、二叉树详解

2.1 二叉树概念

二叉树是每个节点最多有两个子节点的树结构,且子树有左右之分,次序不能任意颠倒。

2.2 特殊二叉树类型

满二叉树

  • 所有非叶子节点都有两个子节点
  • 第k层最多有2^(k-1)个节点
  • 深度为k的满二叉树共有2^k - 1个节点

完全二叉树

  • 除最后一层外,其他层节点数都达到最大
  • 最后一层节点从左到右连续排列
  • 高效的存储结构,适合用数组实现

2.3 二叉树重要性质

  1. 第i层最多有2^(i-1)个节点

  2. 深度为k的二叉树最多有2^k - 1个节点

  3. 叶子节点数n₀ = 度为2的节点数n₂ + 1

  4. 具有n个节点的完全二叉树深度为⌊log₂n⌋ + 1

  5. 数组表示时,节点i的:

    • 父节点:(i-1)/2
    • 左孩子:2*i + 1
    • 右孩子:2*i + 2

三、二叉树遍历算法

3.1 遍历方式概述

四种基本遍历方式:

  • 前序遍历:根 → 左 → 右
  • 中序遍历:左 → 根 → 右
  • 后序遍历:左 → 右 → 根
  • 层序遍历:按层次从上到下、从左到右

3.2 遍历代码实现

java

public class BinaryTree {
    // 节点定义
    static class TreeNode {
        char val;
        TreeNode left;
        TreeNode right;
        
        public TreeNode(char val) {
            this.val = val;
        }
    }
    
    // 创建示例二叉树
    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');
        
        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        C.right = F;
        
        return A;
    }
    
    // 前序遍历
    public void preOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        System.out.print(root.val + " ");  // 访问根节点
        preOrder(root.left);               // 遍历左子树
        preOrder(root.right);              // 遍历右子树
    }
    
    // 中序遍历
    public void inOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        inOrder(root.left);                // 遍历左子树
        System.out.print(root.val + " ");  // 访问根节点
        inOrder(root.right);               // 遍历右子树
    }
    
    // 后序遍历
    public void postOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        postOrder(root.left);              // 遍历左子树
        postOrder(root.right);             // 遍历右子树
        System.out.print(root.val + " ");  // 访问根节点
    }
}

3.3 遍历过程图解

前序遍历过程(根 → 左 → 右):

text

AB → D → 空返回 → D右空返回 → E → 空返回 → E右空返回 → C → 空返回 → F → 空返回 → F右空返回
结果:A B D E C F

中序遍历过程(左 → 根 → 右):

text

空返回 → D → 空返回 → B → 空返回 → E → 空返回 → A → 空返回 → C → 空返回 → F → 空返回
结果:D B E A C F

后序遍历过程(左 → 右 → 根):

text

空返回 → 空返回 → D → 空返回 → 空返回 → E → B → 空返回 → 空返回 → 空返回 → F → C → A
结果:D E B F C A

四、二叉树操作实现

4.1 节点统计操作

java

public class BinaryTreeOperations {
    private TreeNode root;
    
    // 构造方法
    public BinaryTreeOperations(TreeNode root) {
        this.root = root;
    }
    
    // 方法1:遍历思路统计节点个数
    private int nodeSize = 0;
    public int sizeByTraversal(TreeNode root) {
        nodeSize = 0; // 重置计数器
        sizeTraversalHelper(root);
        return nodeSize;
    }
    
    private void sizeTraversalHelper(TreeNode root) {
        if (root == null) {
            return;
        }
        nodeSize++;
        sizeTraversalHelper(root.left);
        sizeTraversalHelper(root.right);
    }
    
    // 方法2:子问题思路统计节点个数
    public int sizeBySubproblem(TreeNode root) {
        if (root == null) {
            return 0;
        }
        // 当前节点 + 左子树节点数 + 右子树节点数
        return 1 + sizeBySubproblem(root.left) + sizeBySubproblem(root.right);
    }
    
    // 方法3:遍历思路统计叶子节点
    private int leafSize = 0;
    public int getLeafCountByTraversal(TreeNode root) {
        leafSize = 0;
        leafTraversalHelper(root);
        return leafSize;
    }
    
    private void leafTraversalHelper(TreeNode root) {
        if (root == null) {
            return;
        }
        if (root.left == null && root.right == null) {
            leafSize++;
        }
        leafTraversalHelper(root.left);
        leafTraversalHelper(root.right);
    }
    
    // 方法4:子问题思路统计叶子节点
    public int getLeafCountBySubproblem(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        }
        return getLeafCountBySubproblem(root.left) + getLeafCountBySubproblem(root.right);
    }
    
    // 获取第K层节点个数
    public int getKLevelNodeCount(TreeNode root, int k) {
        if (root == null || k <= 0) {
            return 0;
        }
        if (k == 1) {
            return 1;
        }
        return getKLevelNodeCount(root.left, k - 1) + getKLevelNodeCount(root.right, k - 1);
    }
    
    // 获取二叉树高度
    public int getHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = getHeight(root.left);
        int rightHeight = getHeight(root.right);
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

4.2 节点查找操作

java

public class BinaryTreeSearch {
    // 查找值为val的节点
    public TreeNode find(TreeNode root, char val) {
        if (root == null) {
            return null;
        }
        
        // 检查当前节点
        if (root.val == val) {
            return root;
        }
        
        // 在左子树中查找
        TreeNode leftResult = find(root.left, val);
        if (leftResult != null) {
            return leftResult;
        }
        
        // 在右子树中查找
        TreeNode rightResult = find(root.right, val);
        if (rightResult != null) {
            return rightResult;
        }
        
        return null;
    }
    
    // 优化版本:使用全局变量记录查找结果
    private TreeNode result = null;
    public TreeNode findOptimized(TreeNode root, char val) {
        result = null;
        findHelper(root, val);
        return result;
    }
    
    private void findHelper(TreeNode root, char val) {
        if (root == null || result != null) {
            return;
        }
        if (root.val == val) {
            result = root;
            return;
        }
        findHelper(root.left, val);
        findHelper(root.right, val);
    }
}

五、高级遍历算法

5.1 层序遍历实现

java

import java.util.LinkedList;
import java.util.Queue;

public class LevelOrderTraversal {
    
    // 层序遍历
    public void levelOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            TreeNode current = queue.poll();
            System.out.print(current.val + " ");
            
            if (current.left != null) {
                queue.offer(current.left);
            }
            if (current.right != null) {
                queue.offer(current.right);
            }
        }
    }
    
    // 带层次信息的层序遍历
    public void levelOrderWithLevel(TreeNode root) {
        if (root == null) {
            return;
        }
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            System.out.print("Level " + ": ");
            
            for (int i = 0; i < levelSize; i++) {
                TreeNode current = queue.poll();
                System.out.print(current.val + " ");
                
                if (current.left != null) {
                    queue.offer(current.left);
                }
                if (current.right != null) {
                    queue.offer(current.right);
                }
            }
            System.out.println();
        }
    }
}

5.2 判断完全二叉树

java

public class CompleteBinaryTree {
    
    // 判断是否为完全二叉树
    public boolean isCompleteTree(TreeNode root) {
        if (root == null) {
            return true;
        }
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        boolean foundNull = false;
        
        while (!queue.isEmpty()) {
            TreeNode current = queue.poll();
            
            if (current == null) {
                foundNull = true;
            } else {
                // 如果之前已经遇到过空节点,现在又遇到非空节点,则不是完全二叉树
                if (foundNull) {
                    return false;
                }
                queue.offer(current.left);
                queue.offer(current.right);
            }
        }
        
        return true;
    }
    
    // 优化的完全二叉树判断
    public boolean isCompleteTreeOptimized(TreeNode root) {
        if (root == null) {
            return true;
        }
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            
            if (node != null) {
                queue.offer(node.left);
                queue.offer(node.right);
            } else {
                // 遇到空节点后,队列中剩余节点必须都为空
                while (!queue.isEmpty()) {
                    if (queue.poll() != null) {
                        return false;
                    }
                }
            }
        }
        
        return true;
    }
}

六、完整测试示例

java

public class BinaryTreeDemo {
    public static void main(String[] args) {
        // 创建二叉树
        BinaryTree tree = new BinaryTree();
        TreeNode root = tree.createTree();
        
        System.out.println("=== 遍历演示 ===");
        System.out.print("前序遍历: ");
        tree.preOrder(root);
        System.out.println();
        
        System.out.print("中序遍历: ");
        tree.inOrder(root);
        System.out.println();
        
        System.out.print("后序遍历: ");
        tree.postOrder(root);
        System.out.println();
        
        System.out.print("层序遍历: ");
        LevelOrderTraversal levelTree = new LevelOrderTraversal();
        levelTree.levelOrder(root);
        System.out.println();
        
        System.out.println("\n=== 节点统计 ===");
        BinaryTreeOperations operations = new BinaryTreeOperations(root);
        System.out.println("节点个数(遍历): " + operations.sizeByTraversal(root));
        System.out.println("节点个数(子问题): " + operations.sizeBySubproblem(root));
        System.out.println("叶子节点数(遍历): " + operations.getLeafCountByTraversal(root));
        System.out.println("叶子节点数(子问题): " + operations.getLeafCountBySubproblem(root));
        System.out.println("第2层节点数: " + operations.getKLevelNodeCount(root, 2));
        System.out.println("树的高度: " + operations.getHeight(root));
        
        System.out.println("\n=== 节点查找 ===");
        BinaryTreeSearch search = new BinaryTreeSearch();
        TreeNode found = search.find(root, 'E');
        if (found != null) {
            System.out.println("找到节点: " + found.val);
        } else {
            System.out.println("节点未找到");
        }
        
        System.out.println("\n=== 完全二叉树判断 ===");
        CompleteBinaryTree completeCheck = new CompleteBinaryTree();
        System.out.println("是否为完全二叉树: " + completeCheck.isCompleteTree(root));
        
        System.out.println("\n=== 带层次的层序遍历 ===");
        levelTree.levelOrderWithLevel(root);
    }
}

七、二叉树应用场景

7.1 实际应用

  1. 文件系统:目录结构
  2. 数据库索引:B树、B+树
  3. 编译器:语法分析树
  4. 游戏开发:场景图管理
  5. 网络路由:路由表查找

7.2 算法应用

  1. 二叉搜索树:快速查找、插入、删除
  2. :优先队列实现
  3. Huffman树:数据压缩
  4. 表达式树:数学表达式求值

八、学习建议

8.1 重点掌握

  1. 递归思维:二叉树问题大多适合递归解决
  2. 遍历算法:前中后序和层序遍历是基础
  3. 性质应用:利用二叉树性质优化算法
  4. 空间复杂度:理解递归调用的栈空间消耗

8.2 练习建议

  1. 手动绘制遍历过程,理解递归栈
  2. 实现各种统计和查找操作
  3. 尝试解决LeetCode二叉树相关题目
  4. 理解完全二叉树与堆的关系

通过系统学习二叉树,你将为学习更复杂的数据结构(如AVL树、红黑树等)打下坚实基础。建议多动手实现,加深理解。