算法很美笔记(Java)——树

news2025/3/15 21:07:56

性质

上面的性质因为两个结点由一条边连成

结点数目越多,算法复杂度越高 

二叉树

 

结构

 层次遍历

利用队列,弹一个,加N个(队列里弹出一个元素,就把这个元素的所有孩子加进去)

具体来说:指针先指树根,加入队列里后,弹出队列,把他的孩子都加入,再弹,再加

二叉查找树(BST)

比root小的放左边,大的放右边

中序遍历会得到递增的有序序列

结构


// 定义二叉树节点的类
class Node {
    int val;
    Node lchild; // 左子树
    Node rchild; // 右子树
 
    // 构造函数,初始化节点的值和子树
    Node(int val) {
        this.val = val;
        this.lchild = null;
        this.rchild = null;
    }
}

// 定义二叉搜索树的类
public class BST {
    private Node root; // 根节点
    private int size;  // 节点个数
 
    // 构造函数,初始化根节点为null
    public BST() {
        this.root = null;
        this.size = 0;
    }
 
    // 判断二叉搜索树是否为空
    public boolean isEmpty() {
        return root == null;
    }
 
    // 获取二叉搜索树的节点个数
    public int size() {
        return size;
    }
 
    // 清空二叉搜索树
    public void clear() {
        this.root = null;
    }
}

 BST类里的方法

只要有修改了树的方法,就会有返回节点,每次返回都要更新树(也就是node.rchild = method(……))

eg

删除(需要改变树)
if (val < root.val) {
            root.lchild = BSTDelete(root.lchild, val);
        } else if (val > root.val) {
            root.rchild = BSTDelete(root.rchild, val);
        } else {

添加(需要改变树)

if (val < node.val) {
            node.lchild = addNode(node.lchild, val);
        } else if (val > node.val) {
            node.rchild = addNode(node.rchild, val);
        }

搜索(不需要改变树)

} else if (val < root.val) {
            return BSTSearch(root.lchild, val);
        } else {
            return BSTSearch(root.rchild, val);

添加

都添加成树叶,不会在中间添加

// 添加节点的方法
    public void addNode(int val) {
        this.root = addNode(this.root, val);
        this.size++;
    }
 
    // 递归插入节点的方法
    private Node addNode(Node node, int val) {
        if (node == null) {
            return new Node(val);
        }
        if (val < node.val) {
            node.lchild = addNode(node.lchild, val);
        } else if (val > node.val) {
            node.rchild = addNode(node.rchild, val);
        }
        return node;
    }

删除

需要考虑三种情况:

  1. 要删除的节点是叶子节点:直接删除该节点。
  2. 要删除的节点只有一个子节点:用该子节点替换要删除的节点。
  3. 要删除的节点有两个子节点:找到该节点右子树中的最小节点(即右子树中最左边的节点),用这个最小节点的值替换要删除节点的值,然后删除右子树中的最小节点。
public void BSTDelete(int val) {
        int originalSize = this.size;
        this.root = BSTDelete(this.root, val);
//因为会有树为空,删除失败的情况,所以不能直接size--
        if (originalSize > this.size) {
            this.size--;
        }
    }

public Node BSTDelete(Node root, int val) {
        if (root == null) {
            return null;
        }
// 递归地在左子树中查找要删除的节点
        if (val < root.val) {
            root.lchild = BSTDelete(root.lchild, val);
        } else if (val > root.val) {
// 递归地在右子树中查找要删除的节点
            root.rchild = BSTDelete(root.rchild, val);
        }
// 找到要删除的节点
        else {
            // 情况 1: 要删除的节点没有子节点或只有一个子节点
            if (root.lchild == null) {
                return root.rchild;
            } else if (root.rchild == null) {
                return root.lchild;
            }
            // 情况 2: 要删除的节点有两个子节点
            // 找到右子树中的最小节点
            root.val = Min(root.rchild);
            // 删除右子树中的最小节点
            root.rchild = BSTDelete(root.rchild, root.val);
        }
        return root;
    }

搜索

public Node BSTSearch(Node root, int val) {
        if (root == null) {
            return null;
        }
        if (val == root.val) {
            return root;
        } else if (val < root.val) {
            return BSTSearch(root.lchild, val);
        } else {
            return BSTSearch(root.rchild, val);
        }
    }

创建

这里没有维护size

public Node BSTBuild(int[] nums) {
        if (nums == null || nums.length == 0) {
            return null;
        }
        this.root = new Node(nums[0]);
        for (int i = 1; i < nums.length; i++) {
            addNode(nums[i]);
        }
        return this.root;
    }

最大值

最右边的值最大

所以我们可以用一个指针p指向root,而后一直移动指针,直到p.right == null

或者用递归

    //    找最大,树的最右节点
    public int Max(){
        if (root == null){
            return -100000;
        }
        TreeNode node = findMax(root);
        return node.val;
    }
    private TreeNode findMax(TreeNode root){
        if(root.right == null){
            return root;
        }
        return findMax(root.right);
    }

最小值

最左边的值最小

同上

    //    找最小,树的最左节点
    public int Min(){
        if (root == null){
            return 100000;
        }
        TreeNode node = findMin(root);
        return node.val;
    }
    private TreeNode findMin(TreeNode root){
        if(root.left == null){
            return root;
        }
        return findMin(root.left);
    }

contains

  
 
    public boolean contains(TreeNode root, int val) {
        if(root == null){
            return false;
        }
        if(root.val == val){
            return true;
        } else if (root.val > val) {
            return contains(root.left,val);
        } else {
            return contains(root.right,val);
        }
    }

某节点的前驱

指的是中序遍历后的那个序列,某节点的前驱

就是比这个节点小的第一个节点

两种情况:1、是其左子树的最大值

                  2、没有左子树,则向上追溯,直到某个祖先节点是右孩子,那么这个祖先节点的父节点就是所求

某节点的后继

指的是中序遍历后的那个序列,某节点的后继

就是比这个节点大的第一个节点

两种情况:1、是其右子树的最小值

                  2、没有右子树,则向上追溯,直到某个祖先节点是左孩子,那么这个祖先节点的父节点就是所求

某节点高度

递归得到左右子树的高度,取较高的一方+1就是某节点的高度

public int getHeight(Node node) {
        if (node == null) return 0;
        int l = getHeight(node.left);
        int r = getHeight(node.right);
        return 1 + Math.max(l, r);
    }

层次遍历

与树的层次遍历思路一致,只不过孩子列表明确成了左右孩子

平衡二叉树(AVL)

左右子树的高度差(平衡因子)不大于1

AVL也是BST,只不过多了一个高度差的特点,所以基本操作实现思路按BST进行就行,同时考虑不同点即可,这里,我们直接复用BST的操作

平衡因子

public int getHeight(Node node) {
        if (node == null) return 0;
        return 1 + Math.max(getHeight(node.lchild), getHeight(node.rchild));
    }
public int getBalanceFactor(Node node) {
        if (node == null) {
            return 0;
        }
        int leftHeight = getHeight(node.left);
        int rightHeight = getHeight(node.right);
        return leftHeight - rightHeight;
    }

如果节点结构里有height,则可以直接调用:

但是如果这个节点是改变后的,想要更新height,就只能用上面的,不能用下面这个方法(记录过的height)

   //获取当前节点的高度
    public int getHeight(Node node){
        if (node==null){
            return 0;
        }
        return node.height;
    }
    //获取当前节点的平衡因子
    public int getBalanceFactor(Node node){
        if (node==null){
            return 0;
        }
        return getHeight(node.left)-getHeight(node.right);
    }

添加

先复用BST的插入,再调整平衡

 // AVL 插入操作
    public void insert(int val) {
        root = bstInsert(root, val);
        root = balanceTree(root);
    }

删除


    // AVL 删除操作
    public void delete(int val) {
        root = bstDelete(root, val);
        root = balanceTree(root);
    }

调整平衡

判断不平衡类型的关键在于当前不平衡节点(平衡因子为 -2 或 2 的节点)及其子节点的平衡因子。

1. LL 型

  • 判断条件:当前不平衡节点的平衡因子为 2,且其左子节点的平衡因子为 1。
  • 调整方法:右旋

2. LR 型

  • 判断条件:当前不平衡节点的平衡因子为 2,且其左子节点的平衡因子为 -1。
  • 调整方法:左旋+右旋

3. RR 型

  • 判断条件:当前不平衡节点的平衡因子为 -2,且其右子节点的平衡因子为 -1。
  • 调整方法:左旋

4. RL 型

  • 判断条件:当前不平衡节点的平衡因子为 -2,且其右子节点的平衡因子为 1。
  • 调整方法:右旋+左旋

检查每个节点(用递归来实现)是否平衡,不平衡就调整 

/ 调整树的平衡
    public Node balanceTree(Node node) {
        if (node == null) {
            return node;
        }
        node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
        int balance = getBalanceFactor(node);
        // LL 型
        if (balance > 1 && getBalanceFactor(node.left) >= 0) {
            return rightRotate(node);
        }
        // LR 型
        if (balance > 1 && getBalanceFactor(node.left) < 0) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }
        // RR 型
        if (balance < -1 && getBalanceFactor(node.right) <= 0) {
            return leftRotate(node);
        }
        // RL 型
        if (balance < -1 && getBalanceFactor(node.right) > 0) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }
        return node;
    }

右旋和左旋

// 右旋操作
    private Node rightRotate(Node y) {
//        实际上就是x和y的位置要改变,
//        让x成为y的父节点
//        没改变前y是x的父节点
        Node x = y.left;
//        如果有T2,就连给y,没有的话T2就是null,y的左孩子就是null
        Node T2 = x.right;

        x.right = y;
        y.left = T2;
//      更新高度
        y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
        x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;

        return x;
    }

    // 左旋操作
    private Node leftRotate(Node x) {
        Node y = x.right;
        Node T2 = y.left;

        y.left = x;
        x.right = T2;

        x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
        y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;

        return y;
    }

带parent的AVL

方法具体实现看这篇文章:https://blog.csdn.net/jarvan5/article/details/112428036

题(未完待续)

叶子节点的个数

public int count(Node root) {
        if (root == null) {
            return 0;
        } else if (root.left == null && root.right == null) {
            return 1;
        }
        return count(root.left) + count((root.right));
    }

第k层的节点数

一个节点的孩子节点的上一层就是这个节点所在层

所以计算第k层所有节点的孩子节点的上一层结点数,即为所求

public int countK(Node root,int k) {
        if (root == null) {
            return 0;
        }
        if(k == 1) {
            return 1;
        }
        return countK(root.left , k-1) + countK(root.right , k-1);
    }

是否是完全二叉树

是否相同

要判断两棵树是否相同,必须同时满足以下两个条件:

  1. 结构相同:两棵树的节点位置和父子关系必须一致。

  2. 节点值相同:对应位置的节点值必须相等。

递归

public boolean isSameTree(Node p, Node 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);
    }

迭代

public boolean isSameTree(Node p, Node q) {
        // 使用栈来存储需要比较的节点对
        Stack<Node[]> stack = new Stack<>();
        stack.push(new Node[]{p, q});

        while (!stack.isEmpty()) {
            Node[] nodes = stack.pop();
            Node node1 = nodes[0];
            Node node2 = nodes[1];

            // 如果两个节点都为空,继续比较下一对节点
            if (node1 == null && node2 == null) {
                continue;
            }
            // 如果一个节点为空,另一个不为空,则不同
            if (node1 == null || node2 == null) {
                return false;
            }
            // 比较当前节点的值
            if (node1.val != node2.val) {
                return false;
            }
            // 将左右子节点对压入栈中
            stack.push(new Node[]{node1.left, node2.left});
            stack.push(new Node[]{node1.right, node2.right});
        }

        return true;
    }

注意:

中序遍历的结果不能唯一确定一棵树的结构,因此不能直接用来判断两棵树是否相同

反例

但中序遍历搭配前序或者后序遍历,即可唯一确定一棵树

所以可以比较两种遍历的结果是否一致,一致就相同

但这样需要开辟空间存遍历结果,所以这种方法不太好(List<Integer>存结果,return pPreOrder.equals(qPreOrder) && pInOrder.equals(qInOrder); 比较)

存结果

是否镜像

判断相同是left和left比,right和right比

判断镜像是left和right比,right和left比

public boolean isMirrorTree(Node p, Node q) {
    // 如果两个节点都为空,则相同
    if (p == null && q == null) {
        return true;
    }
    // 如果一个节点为空,另一个不为空,则不同
    if (p == null || q == null) {
        return false;
    }
    // 比较当前节点的值
    if (p.val != q.val) {
        return false;
    }
    // 递归比较左右子树
    return isMirrorTree(p.left, q.right) && isMirrorTree(p.right, q.left);
}

翻转二叉树(二叉树的镜像)

左右反转二叉树的节点

public Node reverseTree(Node root) {
//        从下到上翻转
        if (root == null) {
            return null;
        }
        // 交换当前节点的左右子节点
        Node temp = root.left;
        root.left = root.right;
        root.right = temp;

        // 递归地反转左子树
        reverseTree(root.left);
        // 递归地反转右子树
        reverseTree(root.right);

        return root;

    }

前序遍历

public void pre(Node root) {
        if (root == null) {
            return;
        }
        System.out.print(root.val + " ");
        pre(root.left);
        pre(root.right);
    }

后序遍历

public void post(Node root) {
        if (root == null) {
            return;
        }
        post(root.left);
        post(root.right);
        System.out.print(root.val + " ");
    }

BST区间搜索

给定一个区间范围,返回所有在这个区间的值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2297431.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百度 API 教程 001:显示地图并添加控件

目录 01、基本使用 前期准备 显示地图 开启鼠标滚轮缩放地图 02、添加地图控件 添加标准地图控件 添加多个控件 网址&#xff1a;地图 JS API | 百度地图API SDK 01、基本使用 前期准备 注册百度账号 申请成为开发者 获取密钥&#xff1a;控制台 | 百度地图开放平台…

OSCP - Other Machines - Loly

主要知识点 路径枚举内核漏洞提权 具体步骤 继续nmap一下先 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-01 07:12 CST Nmap scan report for loly.lc (172.16.33.25) Host is up (0.022s latency). Not shown: 65534 closed tcp ports (conn-refused) PORT …

使用瑞芯微RK3588的NPU进行模型转换和推理

使用边缘设备进行算法落地时&#xff0c;通常要考虑模型推理速度&#xff0c;NVIDA系列平台可以使用TensorRT和CUDA加速&#xff0c;瑞芯微RK3588的板子上都是Arm的手机GPU&#xff0c;虽然没有类似CUDA的加速计算方式&#xff0c;但是提供了NPU进行加速推理&#xff0c;本文说…

我用AI做数据分析之四种堆叠聚合模型的比较

我用AI做数据分析之四种堆叠聚合模型的比较 这里AI数据分析不仅仅是指AI生成代码的能力&#xff0c;我想是测试AI数据分析方面的四个能力&#xff0c;理解人类指令的能力、撰写代码的能力、执行代码的能力和解释结果的能力。如果这四个能力都达到了相当的水准&#xff0c;才可…

AcWing 5166:对称山脉 ← 动态规划

【题目来源】 https://www.luogu.com.cn/problem/P9325 https://www.acwing.com/problem/content/5169/ 【题目描述】 有 N 座山排成一排&#xff0c;从左到右依次编号为 1∼N。 其中&#xff0c;第 i 座山的高度为 hi。 对于一段连续的山脉&#xff0c;我们使用如下方法定义该…

KEPServerEX 的接口类型与连接方式的详细说明

目录 一、KEPServerEX 核心架构 二、KEPServerEX 支持的接口类型 三、KEPServerEX 支持的连接类型 1. 通用工业协议 2. 品牌专属协议 3. 行业专用协议 4. 数据库与文件接口 四、配置示例 1. 接口配置&#xff08;以OPC UA为例&#xff09; 2. 连接配置&#xff08;以…

云原生AI Agent应用安全防护方案最佳实践(上)

当下&#xff0c;AI Agent代理是一种全新的构建动态和复杂业务场景工作流的方式&#xff0c;利用大语言模型&#xff08;LLM&#xff09;作为推理引擎。这些Agent代理应用能够将复杂的自然语言查询任务分解为多个可执行步骤&#xff0c;并结合迭代反馈循环和自省机制&#xff0…

物联网软件开发与应用方向应该怎样学习,学习哪些内容,就业方向是怎样?(文末领取整套学习视频,课件)物联网硬件开发与嵌入式系统

随着物联网技术的飞速发展&#xff0c;物联网软件开发与应用方向成为了众多开发者关注的焦点。那么&#xff0c;如何在这个领域中脱颖而出呢&#xff1f;本文将为你提供一份详细的学习指南&#xff0c;帮助你从零开始&#xff0c;逐步掌握物联网软件开发与应用的核心技能。 一…

计算机网络-八股-学习摘要

一&#xff1a;HTTP的基本概念 全称&#xff1a; 超文本传输协议 从三个方面介绍HTTP协议 1&#xff0c;超文本&#xff1a;我们先来理解「文本」&#xff0c;在互联网早期的时候只是简单的字符文字&#xff0c;但现在「文本」的涵义已经可以扩展为图片、视频、压缩包等&am…

【天梯赛】L2-001紧急救援(用迪杰斯特拉找出权重和最小的最短路径)

解题反思 尝试DFS&#xff1a;开始使用DFS来遍历求解&#xff0c;但 DFS 存在大量重复计算&#xff0c;像同一节点会被多次访问并重复计算路径信息&#xff0c;导致时间复杂度高&#xff0c;部分测试点未通过 改用迪杰斯特拉&#xff1a;为了求解&#xff0c;设置了很多的辅助…

PortSwigger——WebSockets vulnerabilities

文章目录 一、WebSockets二、Lab: Manipulating WebSocket messages to exploit vulnerabilities三、Lab: Manipulating the WebSocket handshake to exploit vulnerabilities四、Using cross-site WebSockets to exploit vulnerabilities4.1 跨站WebSocket劫持&#xff08;cro…

八、OSG学习笔记-

前一章节&#xff1a; 七、OSG学习笔记-碰撞检测-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/145558132?spm1001.2014.3001.5501 一、了解OSG图元加载显示流程 本章节代码&#xff1a; OsgStudy/wids CuiQingCheng/OsgStudy - 码云 - 开源中国https:…

自己动手实现一个简单的Linux AI Agent

大模型带我们来到了自然语言人机交互的时代 1、安装本地大模型进行推理 下载地址&#xff1a; https://ollama.com/download 部署本地deepseek和嵌入模型 ollama run deepseek-r1:7b2、制定Linux操作接口指令规范 3、编写大模型对话工具 #!/usr/bin/python3 #coding: utf-8…

常见的数据仓库有哪些?

数据仓库(Data Warehouse,简称数仓)是企业用于存储、管理和分析大量数据的重要工具,其核心目标是通过整合和处理数据,为决策提供高质量、一致性和可信度的数据支持。在构建和使用数仓时,选择合适的工具和技术至关重要。以下是常见的数仓工具及其特点的详细介绍: 1. Hiv…

LSTM 学习笔记 之pytorch调包每个参数的解释

0、 LSTM 原理 整理优秀的文章 LSTM入门例子&#xff1a;根据前9年的数据预测后3年的客流&#xff08;PyTorch实现&#xff09; [干货]深入浅出LSTM及其Python代码实现 整理视频 李毅宏手撕LSTM [双语字幕]吴恩达深度学习deeplearning.ai 1 Pytorch 代码 这里直接调用了nn.l…

【EXCEL】【VBA】处理GI Log获得Surf格式的CONTOUR DATA

【EXCEL】【VBA】处理GI Log获得Surf格式的CONTOUR DATA data source1: BH coordination tabledata source2:BH layer tableprocess 1:Collect BH List To Layer Tableprocess 2:match Reduced Level from "Layer"+"BH"data source1: BH coordination…

国产编辑器EverEdit - 光标位置跳转

1 光标位置跳转 1.1 应用场景 某些场景下&#xff0c;用户从当前编辑位置跳转到别的位置查阅信息&#xff0c;如果要快速跳转回之前编辑位置&#xff0c;则可以使用光标跳转相关功能。 1.2 使用方法 1.2.1 上一个编辑位置 跳转到上一个编辑位置&#xff0c;即文本修改过的位…

cv2.Sobel

1. Sobel 算子简介 Sobel 算子是一种 边缘检测算子&#xff0c;通过对图像做梯度计算&#xff0c;可以突出边缘。 Sobel X 方向卷积核&#xff1a; 用于计算 水平方向&#xff08;x 方向&#xff09; 的梯度。 2. 输入图像示例 假设我们有一个 55 的灰度图像&#xff0c;像素…

鸿蒙HarmonyOS NEXT开发:优化用户界面性能——组件复用(@Reusable装饰器)

文章目录 一、概述二、原理介绍三、使用规则四、复用类型详解1、标准型2、有限变化型2.1、类型1和类型2布局不同&#xff0c;业务逻辑不同2.2、类型1和类型2布局不同&#xff0c;但是很多业务逻辑公用 3、组合型4、全局型5、嵌套型 一、概述 组件复用是优化用户界面性能&#…

Windows中使用Docker安装Anythingllm,基于deepseek构建自己的本地知识库问答大模型,可局域网内多用户访问、离线运行

文章目录 Windows中使用Docker安装Anythingllm&#xff0c;基于deepseek构建自己的知识库问答大模型1. 安装 Docker Desktop2. 使用Docker拉取Anythingllm镜像2. 设置 STORAGE_LOCATION 路径3. 创建存储目录和 .env 文件.env 文件的作用关键配置项 4. 运行 Docker 命令docker r…