数据结构与算法:二叉树的学习

news2024/11/18 7:43:41

1.了解树形结构

1.概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  • 有一个特殊的结点,称为根结点,根结点没有前驱结点。

  • 除根结点外,其余结点被分成M(M > 0)个互不相交的集合T1、T2、…、Tm,其中每一个集合Ti (1 <= i <=m) 又是一棵与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。

  • 树是递归定义的。

举个例子,每个人都有家族树,家族树一般长这样:
在这里插入图片描述

而数据结构中的树则像是一颗倒过来的树:

在这里插入图片描述
注意:树形结构中,子树之间不能有交集,否则就不是树形结构

2.树的相关概念

以此图为例:
在这里插入图片描述

  • 结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为3。

  • 树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为3。

  • 叶子结点或终端结点:度为0的结点称为叶结点; 如上图:F、H、I、J、K、L…等节点为叶结点。

  • 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点。

  • 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点。

  • 根结点:一棵树中,没有双亲结点的结点;如上图:A。

  • 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推。

  • 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4。

  • 非终端结点或分支结点:度不为0的结点; 如上图:D、E、G…等节点为分支结点。

  • 兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点。

  • 堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:F、G互为堂兄弟结点。

  • 结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先。

  • 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙。

  • 森林:由m(m>=0)棵互不相交的树组成的集合称为森林。

3.树的表示形式

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如:双亲表示法,孩子表示法、孩子兄弟表示法等等。

双亲表示法
在这里插入图片描述

class Node {
	int data;
	int parent;//双亲位置域
}

孩子兄弟表示法
在这里插入图片描述

class Node {
	int value; // 树中存储的数据
	Node firstChild; // 第一个孩子引用
	Node nextBrother; // 下一个兄弟引用
}

孩子表示法
在这里插入图片描述

class Node {
	int child;
    struct Node *next;
}

2.二叉树

1.定义

一棵二叉树是结点的一个有限集合,该集合为空,或者是由一个根节点加上两棵称为左子树和右子树的二叉树组成。

二叉树的特点:

  • 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  • 二叉树的子树有左右之分,其子树的次序不能颠倒。

注意:对于任意的二叉树都是由以下几种情况复合而成的:
在这里插入图片描述

特殊的二叉树:

满二叉树
在这里插入图片描述

每一层的结点数都达到最大值,则这个二叉树就是满二叉树。

也就是说,如果一个二叉树的层数为K(根节点是第1层),且结点总数是(2^k) -1 ,则它就是满二叉树。

完全二叉树

在这里插入图片描述

完全二叉树是由满二叉树而引出来的。对于深度为K的、有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

满二叉树是一种特殊的完全二叉树。

也就是说:完全二叉树的叶子结点只能出现在最下层和次下层,且最下层的叶子结点从左到右连续;前K-1层是满的二叉树。

2.二叉树的性质

在这里插入图片描述

3.二叉树的存储

顺序存储:

二叉树的顺序存储,就是用一组连续的存储单元存放二叉树中的结点。因此,必须把二叉树的所有结点安排成为一个恰当的序列,结点在这个序列中的相互位置能反映出结点之间的逻辑关系,用编号的方法从树根起,自上层至下层,每层自左至右地给所有结点编号,缺点是有可能对存储空间造成极大的浪费,在最坏的情况下,一个深度为k且只有k个结点的右单支树需要2k-1个结点存储空间。

依据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一地反映出结点之间的逻辑关系,这样既能够最大可能地节省存储空间,又可以利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系。

对于一般的二叉树,如果仍按从上至下和从左到右的顺序将树中的结点顺序存储在一维数组中,则数组元素下标之间的关系不能够反映二叉树中结点之间的逻辑关系,只有增添一些并不存在的空结点,使之成为一棵完全二叉树的形式,然后再用一维数组顺序存储。

例子,如图a,b,c:
在这里插入图片描述
显然,这种存储对于需增加许多空结点才能将一棵二叉树改造成为一棵完全二叉树的存储时,会造成空间的大量浪费,不宜用顺序存储结构。

链式存储:
二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:

// 孩子表示法
class Node {
	int val; // 数据域
	Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
	Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
// 孩子双亲表示法
class Node {
	int val; // 数据域
	Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
	Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
	Node parent; // 当前节点的根节点
}

4.二叉树的遍历

学习二叉树结构,最简单的方式就是遍历。所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题(比如:打印节点内容、节点内容加1)。 遍历是二叉树上最重要的操作之一,是二叉树上进行其它运算之基础。

在遍历二叉树时,如果没有进行某种约定,每个人都按照自己的方式遍历,得出的结果就比较混乱,如果按照某种规则进行约定,则每个人对于同一棵树的遍历结果肯定是相同的。如果N代表根节点,L代表根节点的左子树,R代表根节点的右子树,则根据遍历根节点的先后次序有以下遍历方式:

  • NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点—>根的左子树—>根的右子树。
  • LNR:中序遍历(Inorder Traversal)——根的左子树—>根节点—>根的右子树。
  • LRN:后序遍历(Postorder Traversal)——根的左子树—>根的右子树—>根节点。
    .

特殊的层序遍历:

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

以此树为例:
在这里插入图片描述

前序遍历的结果是:A B D E H C F G
中序遍历的结果是:D B E H A F C G
后续遍历的结果是:D H E B F G C A
层序遍历的结果是:A B C D E F G H

5.二叉树的基本操作

public class TestBinaryTree {
    static class TreeNode {

        public char val;//数据域
        public TreeNode left;//左孩子的引用
        public TreeNode right;//右孩子的引用

        public TreeNode(char val) {
            this.val = val;
        }
    }

    public TreeNode root;//代表二叉树的根节点

    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');
        TreeNode G = new TreeNode('G');
        TreeNode H = new TreeNode('H');

        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        C.left = F;
        C.right = G;
        E.right = H;
        //this.root = A;
        return A;
    }

    //前序遍历 根->左子树->右子树 递归
    public void preOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        System.out.print(root.val + " ");
        preOrder(root.left);
        preOrder(root.right);
    }
/*    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) {
            return list;
        }
        list.add((int) root.val);
        List<Integer> leftTree = preorderTraversal(root.left);
        ret.addAll(leftTree);
        List<Integer> rightTree = preorderTraversal(root.right);
        ret.addAll(rightTree);
        return list;
    }*/



    //中序遍历 左子树->根->右子树 递归
    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 + " ");

    }


    // 获取树中节点的个数
    int size(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftSize = size((root.left));
        int rightSize = size((root.right));
        return leftSize + rightSize + 1;
    }

    public int nodeSize;

    public void size2(TreeNode root) {
        if(root == null) {
            return;
        }
        nodeSize++;
        size2(root.left);
        size2(root.right);
    }

    // 获取叶子节点的个数
    int getLeafNodeCount(TreeNode root) {
        if(root == null) {
            return 0;
        }
        if(root.left == null && root.right == null) {
            return 1;
        }
        int leftSize = size((root.left));
        int rightSize = size((root.right));
        return leftSize + rightSize;
    }


    // 获取第K层节点的个数
    int getKLevelNodeCount(TreeNode root,int k){
        if(root == null) {
            return 0;
        }
        if(k == 1) {
            return 1;
        }
        int leftSize = getKLevelNodeCount(root.left,k-1);
        int rightSize = getKLevelNodeCount(root.right,k-1);
        return leftSize + rightSize;
    }

    // 获取二叉树的高度
    int getHeight(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftHeight = getHeight(root.left);
        int rightHeight = getHeight(root.right);
        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

    }

    // 检测值为value的元素是否存在
    TreeNode find(TreeNode root, int val) {
        if (root == null) {
            return null;
        }
        if(root.val == val) {
            return root;
        }
        TreeNode leftTree = find(root.left,val);
        if(leftTree != null) {
            return leftTree;
        }
        TreeNode rightTree = find(root.right,val);
        if (rightTree != null) {
            return rightTree;
        }
        return null;
    }
    
     //二叉树的层序遍历
    public void levelOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            System.out.print(cur.val + " ");
            if (cur.left != null) {
                queue.offer(cur.left);
            }
            if (cur.right != null) {
                queue.offer(cur.right);
            }
        }
    }
}

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

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

相关文章

《Unity Shader 入门精要》 第7章 基础纹理

第7章 基础纹理 纹理最初的目的就是使用一张图片来控制模型的外观。使用纹理映射技术&#xff08;texture mapping&#xff09;&#xff0c;我们可以把一张图黏在模型表面&#xff0c;逐纹素(texel)&#xff08;纹素的名字是为了和像素进行区分&#xff09;地控制模型的颜色。…

爱了爱了,这是什么神仙级Apache Dubbo实战资料,清晰!齐全!已跪!

都2026年了 还没有用过Dubbo&#xff1f; Dubbo是国内最出名的分布式服务框架&#xff0c;也是 Java 程序员必备的必会的框架之一。Dubbo 更是中高级面试过程中经常会问的技术&#xff0c;面试的时候是不是经常不能让面试官满意&#xff1f;无论你是否用过&#xff0c;你都必须…

Postman(2): postman发送带参数的GET请求

发送带参数的GET请求示例&#xff1a;微信公众号获取access_token接口&#xff0c;业务操作步骤1、打开微信公众平台&#xff0c;微信扫码登录&#xff1a;https://mp.weixin.qq.com/debug/cgi-bin/sandbox?tsandbox/login2、打开微信开放文档&#xff0c;找到获取access_toek…

运放电路中各种电阻的计算-运算放大器

运放电路中各种电阻的计算 在学习运算放大器电路的时候&#xff0c;经常需要计算电路的: 输入阻抗Ri&#xff0c; 输出阻抗Ro&#xff0c; 同相端对地等效电阻RP, 反相端对地等效电阻RN&#xff0c; 这些参数很重要&#xff0c;在学习运放相关电路的时候经常要用到&#…

mysql8+mybatis-plus 查询json格式数据

sql 测试json表CREATE TABLE testjson (id int NOT NULL AUTO_INCREMENT,json_obj json DEFAULT NULL,json_arr json DEFAULT NULL,json_str varchar(100) DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT2 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;IN…

API 网关策略二三事

作者暴渊&#xff0c;API7.ai 技术工程师&#xff0c;Apache APISIX Committer。 近些年随着云原生和微服务架构的日趋发展&#xff0c;API 网关以流量入口的角色在技术架构中扮演着越来越重要的作用。API 网关主要负责接收所有请求的流量并进行处理转发至上游服务&#xff0c;…

【数据结构和算法】认识队列,并实现循环队列

上接前文&#xff0c;我们学习了栈的相关知识内容&#xff0c;接下来&#xff0c;来认识一个与栈类似的&#xff0c;另一种特殊的线性表&#xff0c;队列&#xff0c;本文目的是了解并认识队列这一概念&#xff0c;并实现循环队列 目录 一、认识队列 1.队列的概念 2.队列的实…

入门力扣自学笔记232 C++ (题目编号:1669)

1669. 合并两个链表 题目&#xff1a; 给你两个链表 list1 和 list2 &#xff0c;它们包含的元素分别为 n 个和 m 个。 请你将 list1 中下标从 a 到 b 的全部节点都删除&#xff0c;并将list2 接在被删除节点的位置。 下图中蓝色边和节点展示了操作后的结果&#xff1a; 请…

Docker-harbor私有仓库部署与管理

目录 前言 一、Harbor概述 二、Harbor的特性 三、Harbor的构成 四、Harbor构建Docker私有仓库 环境配置 部署Harbor服务 物理机访问server IP 添加项目并填写项目名称 通过127.0.0.1来登陆和推送镜像 其他客户端上传镜像到Harbor 维护管理Harbor 创建Harbor用户 …

JavaWeb_JavaScript

一、简介 JavaScript 是一门跨平台、面向对象的脚本语言&#xff0c;而Java语言也是跨平台的、面向对象的语言&#xff0c;只不过Java是编译语言&#xff0c;是需要编译成字节码文件才能运行的&#xff1b;JavaScript是脚本语言&#xff0c;不需要编译&#xff0c;由浏览器直接…

GPT-3是精神病患者吗?从心理学角度评估大型语言模型

原文链接&#xff1a;https://www.techbeat.net/article-info?id4494 作者&#xff1a;seven_ 20世纪60年代&#xff0c;麻省理工学院人工智能实验室的Joseph Weizenbaum编写了第一个自然语言处理&#xff08;NLP&#xff09;聊天机器人ELIZA[1]&#xff0c;ELIZA通过使用模式…

linux Redis 集群搭建

在单例模式下继续执行&#xff0c;新增文件夹将之前解压后的文件复制到新增的文件夹中修改配置文件&#xff0c;并放入bin中bind 10.88.99.251&#xff08;ip设置&#xff09;protected-mode yes&#xff08;默认yes&#xff0c;开启保护模式&#xff0c;限制为本地访问&#x…

ASEMI整流桥GBU808在选型的过程中需要注意几点

编辑-Z 型号&#xff1a;GBU808 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;800V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;560V 最大直流阻断电压&#xff08;VDC&#xff09;&#xff1a;800V 最大平均正向整流输出电流&#xf…

为2023年做好 IT 安全防御准备

随着网络安全威胁形势的不断演变&#xff0c;安全运营中心 (SOC) 团队在新年伊始花时间审查他们的战略和关键保护措施。 SOC 团队明白&#xff0c;有效的安全性永远不会是一劳永逸的项目。现有的工具和服务需要不断监控和更新&#xff0c;以确保它们能够抵御当前和新出现的威…

测试人员提高业务掌握度的方案

测试人员除了掌握测试相关技术&#xff0c;比如测试流程、测试用例编写思路、自动化脚本的编写、维护之外&#xff0c;还需要对自己所测试的具体业务进行学习和掌握。 只有这样&#xff0c;才能去涉及灰盒、白盒测试&#xff0c;在测试执行过程中&#xff0c;提高自己分析、定位…

17《Protein Actions Principles and Modeling》-《蛋白质作用原理和建模》中文分享

​结合亲和力具有自由能的分子基础理论&#xff1a;在本章中&#xff0c;我们使用K来衡量结合亲和力。我们可以通过结合曲线(binding curves,)来像这样定义一个结合作用力。这样的曲线能给出配体结合的数量与溶液中配体数量的关系。而结合亲和力K也存在关于分子结构以及能量基础…

zabbix6.0配置邮件告警

1、 配置发送邮件服务器&#xff08;管理媒介Email&#xff09; 2、 创建用户用来接收邮件告警&#xff08;管理用户创建用户&#xff09; 用户 报警媒介 权限 3、 设置触发器报警后的动作&#xff08;配置动作触发器动作创建动作&#xff09; 邮件报警是基…

【C++初阶】六、STL---string模拟实现

目录 一、模拟实现接口总览 二、string模拟实现 2.1 构造函数 2.2 析构函数 2.3 拷贝构造函数 2.3.1 传统写法 2.3.2 现代写法 2.4 赋值运算符重载 2.4.1 传统写法 2.4.2 现代写法 2.5 iterator 2.5.1 begin 2.6 Capacity 2.6.1 size 2.6.2 capacity 2.6.2 emp…

Maven的下载安装配置IDEA详细过程

1. 去官网下载好并且放在同一文件夹下面 下载maven安装包&#xff0c;解压即可使用 &#xff08;下载路径&#xff09;http://maven.apache.org/download.cgi 2. maven配置环境变量 MAVEN_HOME D:\IT\Java\apache-maven-3.8.4&#xff1a;这个写你自己放的目录下 进Path新增 %…

推荐系统之召回集读取服务

5.4 召回集读取服务 学习目标 目标 无应用 无 5.4.1 召回集读取服务 添加一个server的目录 会添加推荐中心&#xff0c;召回读取服务&#xff0c;模型排序服务&#xff0c;缓存服务这里先添加一个召回集的结果读取服务recall_service.pyutils.py中装有自己封装的hbase数据库读…