代码随想录算法训练营第13天 |二叉树的学习

news2024/11/15 17:56:28

目录

二叉树

理论基础

二叉树的分类

1. 满二叉树 (Full Binary Tree)

2. 完全二叉树 (Complete Binary Tree)

3. 平衡二叉树 (Balanced Binary Tree)

5. 二叉搜索树 (Binary Search Tree, BST)

二叉树的存储

1. 链式存储 (Linked Representation)

2. 顺序存储 (Sequential Representation)

遍历方式

深度优先搜索

DFS 的工作原理

基本步骤:

广度优先搜索

BFS 的工作原理

基本步骤:

二叉树的遍历

递归遍历

前序遍历

中序遍历

后序遍历

非递归遍历

前序遍历

中序遍历

后续遍历

层序遍历


二叉树

理论基础

二叉树的分类

1. 满二叉树 (Full Binary Tree)
  • 定义:满二叉树是一种二叉树,除了叶子节点外,每个节点都有两个子节点,深度为h的完美二叉树有2^h - 1个节点
  • 特点:所有非叶子节点都有两个子节点,且所有叶子节点都在同一层。
2. 完全二叉树 (Complete Binary Tree)
  • 定义:完全二叉树是一种二叉树,所有层都被完全填满,除了可能是最后一层,且最后一层的所有节点尽可能靠左。
  • 特点:没有缺失的节点,除非是在最后一层的最右边。
3. 平衡二叉树 (Balanced Binary Tree)
  • 定义:平衡二叉树是一种二叉树,其中每个节点的左子树和右子树的高度差不超过1,空树。
  • 特点:平衡二叉树保证了二叉树的高度尽可能低,从而优化了查找、插入和删除操作的效率。
5. 二叉搜索树 (Binary Search Tree, BST)
  • 定义:二叉搜索树是一种有序二叉树,其中每个节点的值大于其左子树中所有节点的值,小于其右子树中所有节点的值。
  • 特点:在理想情况下(即树是平衡的),查找、插入、删除操作的时间复杂度为O(log n)。

二叉树的存储

二叉树的存储方式有多种,常见的主要有以下两种方法:链式存储顺序存储。每种方法都有其适用场景和优缺点。

1. 链式存储 (Linked Representation)

链式存储是二叉树最常用的存储方式,特别适合用于动态变化的树结构。

特点:

  • 每个节点用一个对象或结构体来表示,包含节点的值、左子节点指针、右子节点指针,和(有时)指向父节点的指针。
  • 链式存储易于动态地添加或删除节点,不需要预先分配大量的连续内存。

节点结构

在典型的链式存储中,每个节点包含三个主要部分:

  • 值/数据域:存储节点的数据。
  • 左指针域:指向左子节点。
  • 右指针域:指向右子节点。
class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;

    TreeNode(int value) {
        this.value = value;
        this.left = null;
        this.right = null;
    }
}
2. 顺序存储 (Sequential Representation)

顺序存储是将二叉树节点按某种规则顺序存储在数组中的方式,通常适用于完全二叉树或满二叉树。

特点

  • 二叉树的节点按照层次从上到下、从左到右存储在数组中。
  • 数组的第一个位置(通常从索引0或1开始)存储根节点,其他节点按照其在二叉树中的位置依次存储。

节点位置关系

假设父节点在数组中的索引为i,则:

  • 左子节点的索引2*i + 1 (或 2*i,如果数组从1开始)
  • 右子节点的索引2*i + 2 (或 2*i + 1,如果数组从1开始)
  • 父节点的索引(i-1)/2 (对于索引从0开始的情况)

遍历方式

与图论中的深度优先搜索广度优先搜索是一致的。

深度优先搜索

深度优先搜索(Depth-First Search,简称DFS)是一种遍历或搜索树或图数据结构的算法。它优先深入到树或图的一个分支的尽可能深的节点,然后再回溯并探索其他分支。这种策略使得DFS能够深入到数据结构的最深处,然后逐层返回,直到遍历所有节点。

前序遍历:中左右

中序遍历:左中右

后序遍历:左右中

DFS 的工作原理

DFS 使用栈结构来实现遍历的过程。栈可以通过显式使用栈数据结构来实现,也可以通过递归调用来隐式实现

基本步骤
  1. 访问当前节点
  2. 标记该节点为已访问,以避免重复访问。
  3. 递归地访问该节点的未被访问的相邻节点,对于树结构来说,这意味着先访问左子树,然后访问右子树。
  4. 当节点的所有相邻节点都已被访问或没有未被访问的相邻节点时,回溯到前一个节点,然后继续访问其他未被访问的节点。
  5. 重复以上步骤,直到所有节点都被访问

递归实现:

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;

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

public class DFS {
    public void depthFirstSearch(TreeNode node) {
        if (node == null) {
            return;
        }

        // 访问当前节点
        System.out.print(node.value + " ");

        // 递归地访问左子节点
        depthFirstSearch(node.left);

        // 递归地访问右子节点
        depthFirstSearch(node.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);

        DFS dfs = new DFS();
        dfs.depthFirstSearch(root);  // 输出: 1 2 4 5 3
    }
}

栈实现:

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;

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

public class DFS {
    public void depthFirstSearch(TreeNode node) {
        if (node == null) {
            return;
        }

        // 访问当前节点
        System.out.print(node.value + " ");

        // 递归地访问左子节点
        depthFirstSearch(node.left);

        // 递归地访问右子节点
        depthFirstSearch(node.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);

        DFS dfs = new DFS();
        dfs.depthFirstSearch(root);  // 输出: 1 2 4 5 3
    }
}

广度优先搜索

广度优先搜索(Breadth-First Search,简称 BFS)是一种遍历或搜索树或图数据结构的算法。与深度优先搜索(DFS)不同,BFS 从起始节点开始,首先访问其所有邻居节点,然后再逐层向下深入到下一级的节点。这种逐层访问的方式确保了在无权重图中,从起始节点到任意其他节点的最短路径可以通过 BFS 得到。

BFS 的工作原理

BFS 使用队列(Queue)数据结构来实现逐层遍历的过程。队列遵循先进先出(FIFO)的原则,确保先进入队列的节点先被处理。

基本步骤
  1. 将起始节点加入队列,并标记为已访问。
  2. 从队列中取出一个节点,访问该节点,并将其所有未被访问的相邻节点加入队列。
  3. 标记这些相邻节点为已访问,以防止重复访问。
  4. 重复步骤 2 和 3,直到队列为空,表示所有节点都已被访问。

使用队列实现:

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

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;

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

public class BFS {
    public void breadthFirstSearch(TreeNode root) {
        if (root == null) {
            return;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            System.out.print(node.value + " ");

            // 将左子节点加入队列
            if (node.left != null) {
                queue.offer(node.left);
            }

            // 将右子节点加入队列
            if (node.right != null) {
                queue.offer(node.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);

        BFS bfs = new BFS();
        bfs.breadthFirstSearch(root);  // 输出: 1 2 3 4 5
    }
}

二叉树的遍历

递归遍历

1.确定递归函数的参数和返回值

2.确定终止条件

3.确定单层递归的逻辑

前序遍历
 public List<Integer> preorderTraversal(TreeNode root) {

        List<Integer> result = new ArrayList<>();

        preorder(root, result);

        return result;
    }

    public void preorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        result.add(root.val);
        preorder(root.left, result);
        preorder(root.right, result);
    }
中序遍历
public List<Integer> inorderTraversal(TreeNode root) {

        List<Integer> result = new ArrayList<>();
        inorder(root, result);
        return result;
    }

    public void inorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        inorder(root.left, result);
        result.add(root.val);
        inorder(root.right, result);
    }
后序遍历
 public List<Integer> postorderTraversal(TreeNode root) {

        List<Integer> result = new ArrayList<>();
        postorder(root, result);

        return result;
    }

    public void postorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        postorder(root.left, result);
        postorder(root.right, result);
        result.add(root.val);
    }

非递归遍历

null在谁前就先访问谁,null后如果还要push就先访问push进去的,再访问null

前序遍历

前序遍历,访问的元素和要处理的元素一致

每次循环操纵栈中第一个节点

 public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        Stack<TreeNode> st = new Stack<>();
        //放入第一个元素,放进栈中
        if (root != null) {
            st.push(root);
        }
        while (!st.empty()) {
            //储存栈中的第一个节点
            TreeNode node = st.peek();
            if (node != null) {
                //因为在后面还要压入一次中节点
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。

            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                st.pop();
                result.add(node.val); // 加入到结果集
            }
        }
        return result;
    }

中序遍历

先看节点的左孩子为空则弹出中间,右孩子也为空则继续弹出前一个节点

左孩子不为空,则继续用栈收集

左孩子为空,弹出中间,右孩子不为空,收集右孩子,再看右孩子的左孩子是否为空

 public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        Stack<TreeNode> st = new Stack<>();
        if (root != null) st.push(root);
        while (!st.empty()) {
            TreeNode node = st.peek();
            if (node != null) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。

                if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                st.pop();
                result.add(node.val); // 加入到结果集
            }
        }
        return result;
    }
后续遍历

将左右交换,然后再将数组颠倒

  public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        Stack<TreeNode> st = new Stack<>();
        if (root != null) st.push(root);
        while (!st.empty()) {
            TreeNode node = st.peek();
            if (node != null) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
                if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)         
                               
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                st.pop();
                result.add(node.val); // 加入到结果集
            }
        }
        return result;
   }

层序遍历

每次循环,先得到一个队列的大小为size,然后得到左右孩子,每次弹出一个,直到弹出size个,

下次循环再得到size

// 102.二叉树的层序遍历
class Solution {
    public List<List<Integer>> resList = new ArrayList<List<Integer>>();

    public List<List<Integer>> levelOrder(TreeNode root) {
        //checkFun01(root,0);
        checkFun02(root);

        return resList;
    }

    //BFS--递归方式
    public void checkFun01(TreeNode node, Integer deep) {
        if (node == null) return;
        deep++;

        if (resList.size() < deep) {
            //当层级增加时,list的Item也增加,利用list的索引值进行层级界定
            List<Integer> item = new ArrayList<Integer>();
            resList.add(item);
        }
        resList.get(deep - 1).add(node.val);

        checkFun01(node.left, deep);
        checkFun01(node.right, deep);
    }

    //BFS--迭代方式--借助队列
    public void checkFun02(TreeNode node) {
        if (node == null) return;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(node);

        while (!que.isEmpty()) {
            List<Integer> itemList = new ArrayList<Integer>();
            int len = que.size();

            while (len > 0) {
                TreeNode tmpNode = que.poll();
                itemList.add(tmpNode.val);

                if (tmpNode.left != null) que.offer(tmpNode.left);
                if (tmpNode.right != null) que.offer(tmpNode.right);
                len--;
            }

            resList.add(itemList);
        }

    }
}

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

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

相关文章

废酸处理业务

废酸处理是指将工业生产过程中产生的废酸进行有效处理&#xff0c;以实现其回收利用或安全排放的过程。这一过程对于环境保护和资源节约具有重要意义。以下是对废酸处理的详细介绍&#xff1a; 一、废酸处理的必要性 废酸中含有大量的有害物质&#xff0c;如重金属离子、有机物…

SAP ERP与长城汽车EDI业务集成案例(SAP CPI平台)

一、项目背景 某智能座舱公司是国内领先的智能座舱领域科技公司&#xff0c;致力于成为智能网联行业变革的领导者和推动者&#xff0c;聚焦整车域控制器产品、智能网联软件产品和运营服务产品&#xff1b; 已建成首条先进的数智化域控制器生产线&#xff0c;为客户提供最优…

零基础学PLC的指令-沿指令(2)

扫描操作数的信号上升沿&#xff1a; 在触点分配的 "IN" 位上检测到正跳变&#xff08;0->1&#xff09;时&#xff0c;该触点的状态为 TRUE。该触点逻辑状态随后与能流输入状态组合以设置能流输出状态。P 触点可以放置在程序段中除分支结尾外的任何位置。 扫描…

【VUE入门级温故知新】一文向您详细介绍~组件注册(选项式API)

大家好&#xff0c;我是DX3906 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘大前端领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; 前面和大家分享了《如何从零开始创建一个 Vue 应用》 《VUE模板语法(超详细讲解)》 《一文向您详细介绍~Vu…

FTP服务器(服务名vsftpd,端口tcp/20和tcp/21)

目录 前言 配置文件 FTP服务器的配置 FTP服务的下载 配置防火墙 编辑配置文件 常用字段&#xff1a; 常用字段&#xff08;匿名用户&#xff09;&#xff1a; 常用字段&#xff08;系统用户&#xff09;&#xff1a; 指定系统用户访问&#xff1a; 编辑名单/etc/vsf…

AI预测体彩排3采取888=3策略+和值012路或胆码测试8月27日升级新模型预测第64弹

经过60多期的测试&#xff0c;当然有很多彩友也一直在观察我每天发的预测结果&#xff0c;得到了一个非常有价值的信息&#xff0c;那就是9码定位的命中率非常高&#xff0c;已到达90%的命中率&#xff0c;这给喜欢打私菜的朋友提供了极高价值的预测结果~当然了&#xff0c;大部…

【GD32 MCU 移植教程】从 GD32F303 移植到 GD32F503

1. 前言 GD32E503 系列是 GD 推出的 Cortex_M33 系列产品&#xff0c;该系列资源上与 GD32F303 兼容度非常 高&#xff0c;本应用笔记旨在帮助您快速将应用程序从 GD32F303 系列微控制器移植到 GD32E503 系列微 控制器。 2. 引脚兼容性 GD32F303 与 GD32E503…

C++研发笔记1——github注册文档

1、第一步&#xff1a;登录网站 GitHub: Let’s build from here GitHub 最新跳转页面如下&#xff1a; 2、选择“sign up”进行注册&#xff0c;并填写设置账户信息 3、创建账户成功之后需要进行再次登录 4、根据实际情况填写个人状态信息 登录完成后页面网站&#xff1a; 5…

大规模预训练语言模型的参数高效微调

人工智能咨询培训老师叶梓 转载标明出处 大规模预训练语言模型&#xff08;PLMs&#xff09;在特定下游任务上的微调和存储成本极高&#xff0c;这限制了它们在实际应用中的可行性。为了解决这一问题&#xff0c;来自清华大学和北京人工智能研究院的研究团队探索了一种优化模型…

[MRCTF2020]pyFlag(详解附送多个python脚本)

Hex&#xff1a; FF D9 5B 53 65 63 72 65 74 20 46 69 6C 65 20 50 61 72 74 20 31 3A 5D ASCII&#xff1a; [Secret File Part 1:] 发现Setsuna.jpg尾部有多余的一部分有左侧窗口pk头&a…

手把手教你GPT-SoVITS V2版本模型教程,内附整合包

首先需要声明的一点就是V1的模型能用在V2上面&#xff0c;但是V2的模型不能用在V1上&#xff0c;并且V1模型在V2上效果不佳&#xff01; 整合包下载地址&#xff1a; GPT-SoVITS V2整合包下载 https://klrvc.com/ GPT-SoVITS V2模型下载网 这次V2更新了以下功能 UVR5&#x…

超声波清洗机哪些品牌好用?小型超声波清洗机推荐

在日常生活中&#xff0c;诸如眼镜、项链和耳环之类的常用小物件&#xff0c;频繁的接触使得它们表面易吸附尘埃&#xff0c;尤其是缝隙里的污垢往往难以手动清除。此时&#xff0c;超声波清洗机成为了理想的清洁助手&#xff0c;它能深入细微之处&#xff0c;带来彻底的清洁体…

【设计模式-策略】

定义 策略模式是一种行为型设计模式&#xff0c;它定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以互相替换&#xff0c;且算法的变化不会影响到使用算法的客户。通过使用策略模式&#xff0c;算法可以在运行时根据需要动态地进行更换&#xff0c;从…

JAVA毕业设计164—基于Java+Springboot+vue3的汽车租赁管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的汽车租赁管理系统(源代码数据库)164 一、系统介绍 本项目前后端分离(可以改为ssm版本)&#xff0c;分为用户、会员、管理员三种角色 1、用户&#xff1a…

破防了!软考小白们的春天,低起点也能赢在起跑线

软考通过率是否真的很低&#xff0c;可以通过官方数据来了解。 一、软考通过率是多少&#xff1f; 首先要说明的是&#xff0c;软考办并没有公布全国考试的通过率。但根据官方公布的报名人数和合格人数可以做一个预估。 浙江软考办官方公布&#xff0c;浙江2022年下半年软考…

c#透明悬浮球实现 从零开始用C#写一个桌面应用程序(三)

目标&#xff1a;透明悬浮球 记录日期&#xff1a;20240308 要求基础&#xff1a;C#语言基础部分事件与委托&#xff0c;c#桌面程序基础操作 注&#xff1a;可见前文 http://t.csdnimg.cn/9uWK8 今天开始做一个悬浮球软件。本以为最难的是让悬浮球的具体功能&#xff0c…

养猫知识!猫罐头好还是猫粮好?宠物医生都在用的猫罐头

有位姐妹刚养猫大概已经快一年了&#xff0c;一直给猫喂的都是干粮&#xff0c;猫咪毛发枯燥&#xff0c;长肉慢。带到医院检查后&#xff0c;我发现猫咪营养不良&#xff0c;吸收能力差&#xff0c;有点软便&#xff0c;我建议她给猫咪喂主食罐。结果猫咪爱吃&#xff0c;而且…

openGuass——对象管理

目录 一、表空间 二、数据库 三、模式:Schema 四、database schema table之间的关系 五、表 六、分区表 七、索引 八、视图 九、序列 十、同义词 十一、约束 一、表空间 自带了两个表空间&#xff1a;pg_default和pg_global。查看命令&#xff1a;\db 默认表空间pg…

AI时代,什么是QPS数据?

自 OpenAI 公司于 2022 年 11 月 30 日发布 ChatGPT 以来&#xff0c;经过 23 年一整年的发展之后&#xff0c;大语言模型的概念已逐渐普及&#xff0c;出现了各种基于大语言模型的周边产品&#xff0c;可以说已经玩的相当花哨了。 在这个AI发展的过程中&#xff0c;不少本地化…

Unity之OpenXR如何使用Netcode实现一个多人VR游戏

前言 Netcode for GameObjects 是专为 Unity 构建的高级网络库,可用于抽象网络逻辑。您可以通过网络会话同时向许多玩家发送 GameObjects 和世界数据。借助 Netcode for GameObjects,您可以专注于构建游戏,而无需考虑低级协议和网络框架。 Netcode框架的核心特性包括: 易…