树--数据结构

一.二叉树

1.相关概念


两种特殊的二叉树:


二叉树的性质:

那么已知孩子 父亲:(i-1)/2.

2.二叉树的存储

(1)链式存储

3.二叉树的基本操作

(1)二叉树的遍历=>递归+非递归



前序遍历:

java 复制代码
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<>();
        if(root==null){
            return list;
        }
        list.add(root.val);

        List<Integer> leftList=preorderTraversal(root.left);
        list.addAll(leftList);
        
        List<Integer> rightList=preorderTraversal(root.right);
        list.addAll(rightList);
        return list;
    }
}

使用栈:

java 复制代码
栈顶弹出即访问,先压右再压左
public void preOrderTraversal(TreeNode root) {
    if (root == null) return;
    
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        System.out.print(node.val + " ");
        
        // 注意:栈是后进先出,所以先压右孩子,再压左孩子
        if (node.right != null) {
            stack.push(node.right);
        }
        if (node.left != null) {
            stack.push(node.left);
        }
    }
}

中序遍历:

java 复制代码
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<>();
        if(root==null){
            return list;
        }
        List<Integer> leftList=inorderTraversal(root.left);
        list.addAll(leftList);
        list.add(root.val);
        List<Integer> rightList=inorderTraversal(root.right);
        list.addAll(rightList);
        return list;
    }
}

使用栈:

java 复制代码
一路向左到底,弹栈访问再走右
public void inOrderIterative(TreeNode root) {
    if (root == null) return;
    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;
    }
}

后序遍历:

java 复制代码
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<>();
        if(root==null){
            return list;
        }
        List<Integer> leftList=postorderTraversal(root.left);
        list.addAll(leftList);
        List<Integer> rightList=postorderTraversal(root.right);
        list.addAll(rightList);
        list.add(root.val);
        return list;
    }
}

使用栈:

java 复制代码
双栈倒序最容易,根右左变左右根
public void postOrderIterative(TreeNode root) {
    if (root == null) return;
    Stack<TreeNode> stack1 = new Stack<>();
    Stack<TreeNode> stack2 = new Stack<>();
    stack1.push(root);
    while (!stack1.isEmpty()) {
        TreeNode node = stack1.pop();
        stack2.push(node);
        // 先压左,再压右,保证stack2弹出时是右→左→根
        if (node.left != null) stack1.push(node.left);
        if (node.right != null) stack1.push(node.right);
    }
    while (!stack2.isEmpty()) {
        System.out.print(stack2.pop().val + " ");
    }
}
遍历类型 访问顺序 核心特点 典型应用场景
前序遍历 根 → 左 → 右 先访问根,再递归左右 复制二叉树、打印目录结构
中序遍历 左 → 根 → 右 二叉搜索树的有序输出 验证 BST、排序输出
后序遍历 左 → 右 → 根 先处理子节点,再处理根节点 删除二叉树、计算子树属性

(2)其他基本操作

求最大深度:

查找元素:
==》左子树查找到就立马返回 不会遍历右子树

4.二叉树相关数据结构题

(1)相同的树

java 复制代码
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p==null&&q==null){
            return true;
        }else if((p!=null&&q==null)||(p==null&&q!=null)){
            return false;
        }else{
            if(p.val!=q.val){
                return false;
            }else{
                return  isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
            }
        }
    }
}

(2)另一棵树的子树

思路:先判断两个树是否相同 若不同 递归拿root的左右子树依次和subRoot进行相同与否的比较

java 复制代码
class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        // 主树空了 → 找不到
        if (root == null) return false;
        return isSameTree(root, subRoot)       // 1. 当前两棵树一模一样
            || isSubtree(root.left, subRoot)   // 2. 在左子树里
            || isSubtree(root.right, subRoot); // 3. 在右子树里
    }

    //判断两棵树是否完全相同
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) return true;
        if (p == null || q == null) return false;
        if (p.val != q.val) return false;
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

(3)翻转二叉树

java 复制代码
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        
        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);
        
        root.left = right;
        root.right = left;
        
        return root;
    }
}

(4)对称二叉树

java 复制代码
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) return true;
        return isMirror(root.left, root.right);
    }

    // 判断 a 和 b 是否镜像对称
    private boolean isMirror(TreeNode a, TreeNode b) {
        // 都空 → 对称
        if (a == null && b == null) return true;
        // 一个空一个不空 → 不对称
        if (a == null || b == null) return false;
        
        // 核心:
        // 1. 值相等
        // 2. a的左 与 b的右 对称
        // 3. a的右 与 b的左 对称
        return a.val == b.val 
            && isMirror(a.left, b.right) 
            && isMirror(a.right, b.left);
    }
}

(5)平衡二叉树

java 复制代码
class Solution {
    public boolean isBalanced(TreeNode root) {
        if (root == null) {
            return true;
        }
        // 1. 看当前节点左右高度差是否 ≤1
        int leftH = getHeight(root.left);
        int rightH = getHeight(root.right);      
        if (Math.abs(leftH - rightH) > 1) {
            return false;
        }    
        // 2. 递归看左、右子树是否也平衡
        return isBalanced(root.left) && isBalanced(root.right);
    }
    
    // 求树高度
    private int getHeight(TreeNode root) {
        if (root == null) return 0;
        int left = getHeight(root.left);
        int right = getHeight(root.right);
        return Math.max(left, right) + 1;
    }
}

(6)二叉树的遍历

根据带 # 的前序遍历字符串 → 构建二叉树 → 中序遍历输出

java 复制代码
import java.util.Scanner;

public class Main {
    // 全局下标,用来遍历字符串,记录当前走到第几个字符
    static int i;

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 循环读取输入(OJ 标准写法)
        while (in.hasNext()) {
            String s = in.next();
            i = 0;                // 每次建树前,下标重置为 0
            Node root = build(s);  // 根据带 # 的前序串构建二叉树
            printTree(root);       // 中序遍历输出
            System.out.println();  // 换行
        }
    }

    /**
     * 根据带 # 的前序遍历字符串,递归构建二叉树
     * 规则:根 → 左 → 右
     * # 表示空节点
     */
    public static Node build(String s) {
        Node root = null;
        // 当前字符不是 #,说明是有效节点
        if (s.charAt(i) != '#') {
            root = new Node(s.charAt(i));  // 创建当前节点
            i++;                           // 下标后移
            root.left = build(s);          // 递归构建左子树
            root.right = build(s);         // 递归构建右子树
        } else {
            // 遇到 #,表示空节点,只移动下标,返回 null
            i++;
        }
        return root;
    }

    /**
     * 中序遍历:左 → 根 → 右
     */
    public static void printTree(Node root) {
        if (root == null) {
            return;
        }
        printTree(root.left);             // 先遍历左子树
        System.out.printf("%c ", root.value);  // 访问根节点
        printTree(root.right);            // 最后遍历右子树
    }
}

/**
 * 二叉树节点类
 */
class Node {
    Node left;   // 左孩子
    Node right;  // 右孩子
    char value;  // 节点值

    public Node(char value) {
        this.value = value;
    }
}

(7)二叉树的层序遍历=》队列记录 注意应用size

a.层序遍历
java 复制代码
public List<List<Integer>> levelOrder2(TreeNode root) {
    // 最终结果:按层存储的二维列表
    List<List<Integer>> ret = new ArrayList<>();
    // 空树直接返回空列表
    if (root == null) {
        return ret;
    }
    // 队列:存储待遍历的节点,用LinkedList实现Queue接口
    Queue<TreeNode> queue = new LinkedList<>();
    // 根节点入队,作为遍历起点
    queue.offer(root);

    // 外层循环:控制遍历的层数,队列不为空说明还有层未处理
    while (!queue.isEmpty()) {
        // 当前层的节点列表
        List<Integer> curRow = new ArrayList<>();
        // 关键:记录当前层的节点总数(队列此时的大小就是当前层的节点数)
        int size = queue.size();

        // 内层循环:遍历当前层的所有节点
        while (size != 0) {
            // 出队当前层的节点
            TreeNode cur = queue.poll();
            // 将节点值加入当前层列表
            curRow.add(cur.val);

            // 左孩子非空,入队(下一层的节点)
            if (cur.left != null) {
                queue.offer(cur.left);
            }
            // 右孩子非空,入队(下一层的节点)
            if (cur.right != null) {
                queue.offer(cur.right);
            }
            // 当前层节点数减1,直到遍历完当前层所有节点
            size--;
        }
        // 将当前层的列表加入最终结果
        ret.add(curRow);
    }
    return ret;
}
b.自底向上的层序遍历
java 复制代码
import java.util.*;

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        // 最终结果:自底向上的层序遍历
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        // 队列用于层序遍历(BFS)
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        // 层序遍历,按「根→叶」的顺序收集每一层
        while (!queue.isEmpty()) {
            // 当前层的节点数
            int levelSize = queue.size();
            // 存储当前层的节点值
            List<Integer> currentLevel = new ArrayList<>();
            // 遍历当前层的所有节点
            for (int i = 0; i < levelSize; i++) {
                TreeNode cur = queue.poll();
                currentLevel.add(cur.val);
                // 左右孩子入队(下一层的节点)
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            // 将当前层加入结果(此时顺序是「根→叶」)
            result.add(currentLevel);
        }
        // 反转结果,得到「叶→根」的自底向上顺序
        Collections.reverse(result);
        return result;
    }
}
c.二叉树右视图

=》每一层的最后一个节点

java 复制代码
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        //存放结果集
        List<Integer> res = new ArrayList<>();
        if (root == null) return res;
        //利用队列
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);

        while (!q.isEmpty()) {
            // 当前层有多少个节点
            int size = q.size();
            // 遍历这一层
            for (int i = 0; i < size; i++) {
                TreeNode cur = q.poll();
                // ✨ 关键:只把每层最后一个加入结果
                if (i == size - 1) {
                    res.add(cur.val);
                }
                // 正常层序入队
                if (cur.left != null) q.offer(cur.left);
                if (cur.right != null) q.offer(cur.right);
            }
        }
        return res;
    }
}
d.完全二叉树
java 复制代码
class Solution {
    public boolean isCompleteTree(TreeNode root) {
        // 空树是完全二叉树
        if (root == null) {
            return true;
        }

        // 队列用于层序遍历
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            if (cur != null) {
               queue.offer(cur.left);
               queue.offer(cur.right);
            } else {
                break;
            }
        }

        // 遍历完所有节点 判断剩余队列里有空 → 是完全二叉树
        while (!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            if (cur != null) {  
                return false;   // 只要发现一个不是null,直接false
            }
        }
        return true;
    }
}

(8)公共最近祖先

java 复制代码
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return null;
        }
        if (root == p || root == q) {
            return root;
        }
        TreeNode leftRet = lowestCommonAncestor(root.left, p, q);
        TreeNode rightRet = lowestCommonAncestor(root.right, p, q);
        if (leftRet != null && rightRet != null) {
            return root;
        } else if (leftRet != null) {
            return leftRet;
        } else {
            return rightRet;
        }
    }
}

或者:

获取路径:

(9)前序中序遍历序列构造二叉树

java 复制代码
class Solution {
    //前序遍历的索引位置
    public int preIndex=0;

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeChild(preorder,inorder,0,inorder.length-1);
    }

    //在中序遍历序列中找root
    public int findRoot(int[] inorder,int inbegin,int inend,int key){
        for(int i=inbegin;i<=inend;i++){
            if(inorder[i]==key){
                return i;
            }
        }
        return -1;
    }
    public TreeNode buildTreeChild(int[] preorder, int[] inorder,int inbegin,int inend) {
        //没有子树
        if(inbegin>inend){
            return null;
        } 
        //从先序遍历确定根节点
        TreeNode root=new TreeNode(preorder[preIndex]);
        //在中序遍历序列中找root
        int rootIndex= findRoot(inorder,inbegin,inend,preorder[preIndex]);
        //指针后移
        preIndex++;
        root.left=buildTreeChild(preorder,inorder,inbegin,rootIndex-1);
        root.right=buildTreeChild(preorder,inorder,rootIndex+1,inend);
        return root;
    }
}

(10)中序后序遍历序列构造二叉树

java 复制代码
class Solution {
    // 后序遍历的指针,初始指向最后一个元素(根节点)
    int postIndex;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        postIndex=postorder.length-1;
        return buildTreChild(inorder,postorder,0,inorder.length-1);
    }
    //中序遍历中找根节点
    public int findval(int[] inorder,int inbegin,int inend,int key){
        for(int i=inbegin;i<=inend;i++){
            if(inorder[i]==key){
                return i;
            }
        }
        return -1;
    }
    public TreeNode buildTreChild(int[] inorder, int[] postorder,int inbegin,int inend) {
        if(inbegin>inend){
            return null;
        }
        // 取后序当前指针的元素,作为根节点
        int rootVal = postorder[postIndex];
        TreeNode root = new TreeNode(rootVal);
        if(root==null){
            return null;
        }
        //接收中序遍历中根节点的索引位置
        int rootIndex =findval(inorder,inbegin,inend,rootVal);
        //指针前移
        postIndex--;
        //先右子树 再左子树 不能反序
        root.right=buildTreChild(inorder,postorder,rootIndex+1,inend);
        root.left=buildTreChild(inorder,postorder,inbegin,rootIndex-1);
        return root;
    }
}

(11)二叉树创建字符串

  • 左子树处理
    • if (root.left != null):有左孩子就加括号递归。
    • else if (root.right != null):如果没左孩子但有右孩子,必须加个空括号 (),这样结果才是 1() 这种格式。
  • 右子树处理
    • 只要有右孩子,就加括号递归。如果没有,就不处理。
情况 处理规则 示例
左子树非空 必须加括号()包裹递归结果 1(2)
左子树空、右子树非空 必须补空括号()占位 1()(3)
左子树空、右子树空 不做任何操作,省略空括号 4(无括号)
右子树非空 必须加括号()包裹递归结果 1(3)
右子树空 不做任何操作,省略空括号 1(2)
java 复制代码
class Solution {
    public String tree2str(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        tree2strChild(sb, root);
        return sb.toString();
    }

    public void tree2strChild(StringBuilder sb,TreeNode root){
        if(root==null){
            return;
        }
        // 先拼自己!!!
        sb.append(root.val);
        // 处理左子树
        if(root.left!=null){
            sb.append("(");
            tree2strChild(sb, root.left);
            sb.append(")");
        }else{
            if(root.right!=null){
                sb.append("()");
            }else{
            return;
            }
        }
        // 处理右子树
        if (root.right != null) {
            sb.append("(");
            tree2strChild(sb, root.right); 
            sb.append(")");
        }
    }
}

5.二叉搜索树

中序遍历是有序的。


查找

java 复制代码
public TreeNode search(TreeNode root, int key) {
    if (root == null) {
        return null;
    }
    TreeNode cur = root;
    while (cur != null) {
        if (cur.val < key) {
            cur = cur.right;
        } else if (cur.val == key) {
            return cur;
        } else {
            cur = cur.left;
        }
    }
    return null;
}

最好情况O(logN):树为满二叉树 / 完全二叉树,树高为logN,最多遍历logN次

最坏情况O(N):树退化为单分支链表,树高为N,最多遍历N次


插入

java 复制代码
public boolean insert(int val) {
    if (root == null) {
        root = new TreeNode(val);
        return true;
    }

    TreeNode node = new TreeNode(val);
    TreeNode cur = root;
    TreeNode parent = null;
    while (cur != null) {
        if (cur.val < val) {
            parent = cur;
            cur = cur.right;
        } else if (cur.val > val) {
            parent = cur;
            cur = cur.left;
        } else {
            return false;
        }
    }

    if (parent.val > val) {
        parent.left = node;
    } else {
        parent.right = node;
    }
    return true; 
}

删除

java 复制代码
public boolean delete(int key) {
    // 1. 查找待删节点cur及其父节点parent
    TreeNode parent = null;
    TreeNode cur = root;
    while (cur != null) {
        if (cur.val == key) {
            break;
        }
        parent = cur;
        if (cur.val < key) {
            cur = cur.right;
        } else {
            cur = cur.left;
        }
    }
    if (cur == null) {
        return false;
    }

    // 2. 调用removeNode删除节点
    removeNode(parent, cur);
    return true;
}


private void removeNode(TreeNode parent, TreeNode cur) {
    // 情况1:左孩子为空(含叶子节点)
    if (cur.left == null) {
        if (cur == root) {
            root = cur.right;
        } else if (cur == parent.left) {
            parent.left = cur.right;
        } else {
            parent.right = cur.right;
        }
    }
    // 情况2:右孩子为空
    else if (cur.right == null) {
        if (cur == root) {
            root = cur.left;
        } else if (cur == parent.left) {
            parent.left = cur.left;
        } else {
            parent.right = cur.left;
        }
    }
    // 情况3:左右孩子都存在(核心难点)
    else {
    TreeNode target = cur.right;
    TreeNode targetParent = cur;
    while (target.left != null) {
        targetParent = target;
        target = target.left;
    }
    cur.val = target.val;

    if (target == targetParent.left) {
        targetParent.left = target.right;
    } else {
        targetParent.right = target.right;
    }
}
    }
}

6.AVL树

7.红黑树

相关推荐
小肝一下3 小时前
每日两道力扣,day7
数据结构·c++·算法·leetcode·双指针·hot100·接雨水,四数之和
‎ദ്ദിᵔ.˛.ᵔ₎1 天前
LIST 的相关知识
数据结构·list
M--Y1 天前
Redis常用数据类型
数据结构·数据库·redis
༾冬瓜大侠༿1 天前
vector
c语言·开发语言·数据结构·c++·算法
汀、人工智能1 天前
[特殊字符] 第58课:两个正序数组的中位数
数据结构·算法·数据库架构··数据流·两个正序数组的中位数
liu****1 天前
第16届省赛蓝桥杯大赛C/C++大学B组(京津冀)
开发语言·数据结构·c++·算法·蓝桥杯
汀、人工智能1 天前
[特殊字符] 第79课:分割等和子集
数据结构·算法·数据库架构·位运算·哈希表·分割等和子集
汀、人工智能1 天前
[特殊字符] 第74课:完全平方数
数据结构·算法·数据库架构·图论·bfs·完全平方数
CoderCodingNo1 天前
【GESP】C++四、五级练习题 luogu-P1177 【模板】排序
数据结构·c++·算法