0

0

JavaScript二叉树(二叉搜索树)的详细介绍

不言

不言

发布时间:2019-01-08 10:15:58

|

3236人浏览过

|

来源于segmentfault

转载

本篇文章给大家带来的内容是关于javascript二叉树(二叉搜索树)的详细介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

可能有一部分人没有读过我上一篇写的二叉堆,所以这里把二叉树的基本概念复制过来了,如果读过的人可以忽略前面针对二叉树基本概念的介绍,另外如果对链表数据结构不清楚的最好先看一下本人之前写的js数据结构-链表

二叉树

二叉树(Binary Tree)是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。而每个分支节点也常常被称作为一棵子树。

bVbmEdC.png

  • 根节点:二叉树最顶层的节点

    立即学习Java免费学习笔记(深入)”;

  • 分支节点:除了根节点以外且拥有叶子节点

  • 叶子节点:除了自身,没有其他子节点

常用术语
在二叉树中,我们常常还会用父节点和子节点来描述,比如图中2为6和3的父节点,反之6和3是2子节点

二叉树的三个性质

  1. 在二叉树的第i层上,至多有2^i-1个节点

  • i=1时,只有一个根节点,2^(i-1) = 2^0 = 1

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

    • i=2时,2^k-1 = 2^2 - 1 = 3个节点

  • 对任何一棵二叉树T,如果总结点数为n0,度为2(子树数目为2)的节点数为n2,则n0=n2+1

  • 树和二叉树的三个主要差别

    • 树的节点个数至少为1,而二叉树的节点个数可以为0

    • 树中节点的最大度数(节点数量)没有限制,而二叉树的节点的最大度数为2

    • 树的节点没有左右之分,而二叉树的节点有左右之分

    二叉树分类

    二叉树分为完全二叉树(complete binary tree)和满二叉树(full binary tree)

    • 满二叉树:一棵深度为k且有2^k - 1个节点的二叉树称为满二叉树

    • 完全二叉树:完全二叉树是指最后一层左边是满的,右边可能满也可能不满,然后其余层都是满的二叉树称为完全二叉树(满二叉树也是一种完全二叉树)

    bVbmEH7.png

    二叉搜索树

    二叉搜索树满足以下的几个性质:

    • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;

    • 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;

    • 任意节点的左、右子树也需要满足左边小右边大的性质

    我们来举个例子来深入理解以下

    一组数据:12,4,18,1,8,16,20
    由下图可以看出,左边的图满足了二叉树的性质,它的每个左子节点都小于父节点,右子节点大于其父节点,同时左子树的节点都小于根节点,右子树的节点都大于根节点

    2264746377-5c3303126cf52_articlex.png

    二叉搜索树主要的几个操作:

    • 查找(search)

    • 插入(insert)

    • 遍历(transverse)

    二叉树搜索树的链式存储结构

    通过下图,可以知道二叉搜索树的节点通常包含4个域,数据元素,分别指向其左,右节点的指针和一个指向父节点的指针所构成,一般把这种存储结构称为三叉链表。
    图片描述

    用代码初始化一个二叉搜索树的结点:

    无限画
    无限画

    千库网旗下AI绘画创作平台

    下载
    • 一个指向父亲节点的指针 parent

    • 一个指向左节点的指针 left

    • 一个指向右节点的指针 right

    • 一个数据元素,里面可以是一个key和value

        class BinaryTreeNode {
            constructor(key, value){
                this.parent = null;
                this.left = null;
                this.right = null;
                this.key = key;
                this.value = value;
            }
        }

    接着我们再用代码去初始化一个二叉搜索树

    • 在二叉搜索树中我们会维护一个root指针,这个就相当于链表中的head指针,在没有任何节点插入的时候它指向空,在有节点插入以后它指向根节点。

        class BinarySearchTree {
            constructor() {
                this.root = null;
            }
        }

    创建节点

        static createNode(key, value) {
            return new BinarySearchTree(key, value);
        }

    插入操作

    看下面这张图,13是我们要插入的节点,它插入的具体步骤:

    1. 跟根节点12做比较,比12大,所以我们确定了,这个节点是往右子树插入的

    2. 而根节点的右边已经有节点,那么跟这个节点18做比较,结果小于18所以往18的左节点找位置

    3. 而18的左节点也已经有节点了,所以继续跟这个节点做比较,结果小于16

    4. 刚好16的左节点是空的(left=null),所以13这个节点就插入到了16的左节点

    995916720-5c3219c38a4eb_articlex.png

    通过上面的描述,我们来看看代码是怎么写的

    • 定义两个指针,分别是p和tail,最初都指向root,p是用来指向要插入的位置的父节点的指针,而tail是用来查找插入位置的,所以最后它会指向null,用上图举个例子,p最后指向了6这个节点,而tail最后指向了null(tail为null则说明已经找到了要插入的位置)

    • 循环,tail根据我们上面分析的一步一步往下找位置插入,如果比当前节点小就往左找,大则往右找,一直到tail找到一个空位置也就是null

    • 如果当前的root为null,则说明当前结构中并没有节点,所以插入的第一个节点直接为跟节点,即this.root = node

    • 将插入后的节点的parent指针指向父节点

        insert(node){
            let p = this.root;
            let tail = this.root;
            
            // 循环遍历,去找到对应的位置
            while(tail) {
                p = tail;
                // 要插入的节点key比当前节点小
                if (node.key < tail.key){
                    tail.left = tail.left;
                }
                // 要插入的节点key比当前节点大
                else {
                    tail.right = tail.right;
                }
            }
            
            // 没有根节点,则直接作为根节点插入
            if(!p) {
                this.root = node;
                return;
            }
            
            // p是最后一个节点,也就是我们要插入的位置的父节点
            // 比父节点大则往右边插入
            if(p.key < node.key){
                p.right = node;
            }
            // 比父节点小则往左边插入
            else {
                p.left = node;
            }
            
            // 指向父节点
            node.parent = p;
    
        }

    查找

    查找就很简单了,其实和插入差多,都是去别叫左右节点的大小,然后往下找

    • 如果root = null, 则二叉树中没有任何节点,直接return,或者报个错什么的。

    • 循环查找

        search(key) {
            let p = this.root;
            if(!p) {
                return;
            }
            
            while(p && p.key !== key){
                if(p.key<key){
                    p = p.right;
                }else{
                    p = p.left;
                }
            }
            
            return p;
        }

    遍历

    • 中序遍历(inorder):先遍历左节点,再遍历自己,最后遍历右节点,输出的刚好是有序的列表

    • 前序遍历(preorder):先自己,再遍历左节点,最后遍历右节点

    • 后序遍历(postorder):先左节点,再右节点,最后自己

    最常用的一般是中序遍历,因为中序遍历可以得到一个已经排好序的列表,这也是为什么会用二叉搜索树排序的原因

    根据上面对中序遍历的解释,那么代码就变的很简单,就是一个递归的过程,递归停止的条件就是节点为null

    • 先遍历左节点-->yield* this._transverse(node.left)

    • 遍历自己 --> yield* node

    • 遍历左节点 --> yield* this._transverse(node.right)

        transverse() {
            return this._transverse(this.root);
        }
        
        *_transverse(node){
            if(!node){
                return;
            }
            yield* this._transverse(node.left);
            yield node;
            yield* this._transverse(node.right)
        }

    496773676-5c33075897e72_articlex.png

    看上面这张图,我们简化的来看一下,先访问左节点4,再自己12,然后右节点18,这样输出的就刚好是一个12,4,8

    补充:这个地方用了generater,所以返回的一个迭代器。可以通过下面这种方式得到一个有序的数组,这里的前提就当是已经有插入的节点了

       const tree = new BinaryTree();
       //...中间省略插入过程
        
       // 这样就返回了一个有序的数组 
       var arr = [...tree.transverse()].map(item=>item.key);

    完整代码

    class BinaryTreeNode {
      constructor(key, value) {
        // 指向父节点
        this.p = null;
    
        // 左节点
        this.left = null;
    
        // 右节点
        this.right = null;
    
        // 键
        this.key = key;
    
        // 值
        this.value = value;
      }
    }
    
    class BinaryTree {
      constructor() {
        this.root = null;
      }
    
      static createNode(key, value) {
        return new BinaryTreeNode(key, value);
      }
    
      search(key) {
        let p = this.root;
        if (!p) {
          return;
        }
    
        while (p && p.key !== key) {
          if (p.key < key) {
            p = p.right;
          } else {
            p = p.left;
          }
        }
    
        return p;
      }
    
      insert(node) {
        // 尾指针的父节点指针
        let p = this.root;
    
        // 尾指针
        let tail = this.root;
    
        while (tail) {
          p = tail;
          if (node.key < tail.key) {
            tail = tail.left;
          } else {
            tail = tail.right;
          }
        }
    
        if (!p) {
          this.root = node;
          return;
        }
    
        // 插入
        if (p.key < node.key) {
          p.right = node;
        } else {
          p.left = node;
        }
    
        node.p = p;
      }
    
      transverse() {
        return this.__transverse(this.root);
      }
    
      *__transverse(node) {
        if (!node) {
          return;
        }
        yield* this.__transverse(node.left);
        yield node;
        yield* this.__transverse(node.right);
      }
    }

    总结

    二叉查找树就讲完了哈,其实这个和链表很像的,还是操作那么几个指针,既然叫查找树了,它主要还是用来左一些搜索,还有就是排序了,另外补充一下,二叉查找树里找最大值和最小值也很方便是不是,如果你大致读懂了的话。

    相关文章

    java速学教程(入门到精通)
    java速学教程(入门到精通)

    java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

    下载

    相关标签:

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    热门AI工具

    更多
    DeepSeek
    DeepSeek

    幻方量化公司旗下的开源大模型平台

    豆包大模型
    豆包大模型

    字节跳动自主研发的一系列大型语言模型

    WorkBuddy
    WorkBuddy

    腾讯云推出的AI原生桌面智能体工作台

    腾讯元宝
    腾讯元宝

    腾讯混元平台推出的AI助手

    文心一言
    文心一言

    文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

    讯飞写作
    讯飞写作

    基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

    即梦AI
    即梦AI

    一站式AI创作平台,免费AI图片和视频生成。

    ChatGPT
    ChatGPT

    最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

    相关专题

    更多
    TypeScript类型系统进阶与大型前端项目实践
    TypeScript类型系统进阶与大型前端项目实践

    本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

    26

    2026.03.13

    Python异步编程与Asyncio高并发应用实践
    Python异步编程与Asyncio高并发应用实践

    本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

    46

    2026.03.12

    C# ASP.NET Core微服务架构与API网关实践
    C# ASP.NET Core微服务架构与API网关实践

    本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

    178

    2026.03.11

    Go高并发任务调度与Goroutine池化实践
    Go高并发任务调度与Goroutine池化实践

    本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

    51

    2026.03.10

    Kotlin Android模块化架构与组件化开发实践
    Kotlin Android模块化架构与组件化开发实践

    本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

    92

    2026.03.09

    JavaScript浏览器渲染机制与前端性能优化实践
    JavaScript浏览器渲染机制与前端性能优化实践

    本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

    102

    2026.03.06

    Rust内存安全机制与所有权模型深度实践
    Rust内存安全机制与所有权模型深度实践

    本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

    227

    2026.03.05

    PHP高性能API设计与Laravel服务架构实践
    PHP高性能API设计与Laravel服务架构实践

    本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

    532

    2026.03.04

    AI安装教程大全
    AI安装教程大全

    2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

    171

    2026.03.04

    热门下载

    更多
    网站特效
    /
    网站源码
    /
    网站素材
    /
    前端模板

    精品课程

    更多
    相关推荐
    /
    热门推荐
    /
    最新课程
    MongoDB 教程
    MongoDB 教程

    共17课时 | 3.3万人学习

    进程与SOCKET
    进程与SOCKET

    共6课时 | 0.4万人学习

    MySQL索引优化解决方案
    MySQL索引优化解决方案

    共23课时 | 2.1万人学习

    关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
    php中文网:公益在线php培训,帮助PHP学习者快速成长!
    关注服务号 技术交流群
    PHP中文网订阅号
    每天精选资源文章推送

    Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号