实现二叉排序树

news2024/9/28 7:17:36

一:二叉树和二叉搜索树

  • 二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。这个定义有助于我们写出更高效地在树中插入、查找和删除节点的算法,二叉树在计算机科学中的应用非常广泛。

  • **二叉搜索树(BST)**是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大的值。

二:实现二叉搜索树

2.1 创建Node类表示二叉搜索树中的每个节点

    //二叉树的存储结构为
    class Node
    {
      constructor(data, left, right)
      {
        this.data = data
        this.left = left
        this.right = right
        //若有相同的元素插入节点,就放弃插入,count++
        this.count = 1
      }
    }

image-20230718103946588

该图为二叉搜索树数据结构的组织方式,对于树,我们使用两个指针,一个指向左侧子节点,一个指向右侧右节点

2.2 创建BSTree 类的基本结构

class BSTree {
    constructor() {
        this.root = null;
    }
 
    // 删除一个节点
    _removeNode(node, data) {
       
    }
 
    // 删除给定的数据节点
    remove(data) {
        this.root = this._removeNode(this.root, data);
    }
 
    // 向二叉树中插入节点
    insert(data) {
        
    }
 
    // 寻找给定数据的节点
    find(data) {
        
    }
 
    // 获得最小值的节点
    getMinNode(node = this.root) {
        
    }
 
    // 获得最大值的节点
    getMaxNode(node = this.root) {
        
    }
}

2.3 实现insert()方法

//向二叉树插入节点
insert (data)
{
    let newNode = new Node(data, null, null)
    //更新根节点的值
    if (this.root === null) {
        this.root = newNode
    } else {
        //更新当前节点的值
        let currentNode = this.root
        //父节点就是空
        let parentNode = null
        while (true) {
            //更新父节点
            parentNode = currentNode
            //判断插入节点值在左子树还是右子树
            if (newNode.data < currentNode.data) {
                //更新当前节点
                currentNode = currentNode.left
                if (!currentNode) {
                    parentNode.left = newNode
                    break
                }
            }

            else if (newNode.data > currentNode.data) {
                currentNode = currentNode.right
                if (!currentNode) {
                    parentNode.right = newNode
                    break
                }
            }

            else if (newNode.data === currentNode.data) {
                //如果相同数据 count++ 不做处理
                currentNode.count++
                break
            }
        }
    }
}
  • 首先创建一个新的节点newNode,该节点包含要插入的数据
  • 检查根节点是否为空,若为空,说明这是第一个插入的节点,将根节点指向newNode
  • 如果根节点不为空,则需要找到合适的位置插入该节点
  • 初始化当前节点currentNode为根节点this.root,并且初始化父节点parentNode为空
  • 进入循环,直到找到合适的位置插入节点或者遇到相同的数据
  • 在每次循环中,更新父节点parentNode为当前节点currentNode
  • 判断要插入的节点值和当前节点值的大小关系
    • 如果要插入的节点值小于当前节点值,说明要插入的节点应该在当前节点的左子树中
      • 更新当前节点为当前节点的左子节点 currentNode.left
      • 如果当前节点的左子节点为空,说明找到了插入位置,将新节点 newNode 设置为当前节点的左子节点,并且跳出循环
    • 如果要插入的节点值大于当前节点值,说明要插入的节点应该在当前节点的右子树中
      • 更新当前节点为当前节点的右子节点 currentNode.right
      • 如果当前节点的右子节点为空,说明找到了插入位置,将新节点 newNode 设置为当前节点的右子节点,并且跳出循环
    • 如果要插入的节点值等于当前节点值,说明要插入的节点与当前节点的值相同。
      • 将当前节点的计数器 count 加一,表示重复出现的次数。
      • 跳出循环。

2.4 实现find()方法

//寻找给定数据的节点
find (data)
{
    let currentNode = this.root
    while (currentNode) {
        if (currentNode.data == data) {
            return currentNode
        } else if (currentNode.data < data) {
            currentNode = currentNode.right
        } else {
            currentNode = currentNode.left
        }
    }
    return null
}

2.5 实现getMinNode()和getMaxNode()方法

//获取最小值
getMinNode (node = this.root)
{
    let currentNode = node
    while (currentNode.left) {
        currentNode = currentNode.left
    }
    return currentNode
}

//获取最大值
getMaxNode (node = this.root)
{
    let currentNode = node
    while (currentNode.right) {
        currentNode = currentNode.right
    }
    return currentNode
}

2.6 实现remove()方法

//删除节点,实例中不应调用
_removeNode (node, data)
{
    if (node === null) {
        return null
    }
    //找到要删除的节点的了 
    if (data === node.data) {
        //分三种情况
        //1. 要删除的节点为叶子结点 
        if (node.left === null && node.right === null) {
            return null
        }

        //2. 没有左子节点的节点
        if (node.left === null) return node.right

        //   没有右子节点的节点
        if (node.right === null) return node.left


        //3.有两个子节点的节点
        //找到待删除节点的右子树的最小值赋值给临时节点tmpNode
        //将tmpNode赋值给node 就说明用右子树的最小值来代替待删除节点
        let tmpNode = this.getMinNode(node.right)
        //tmpNode赋值给待删除节点
        node.data = tmpNode.data
        //删除临时节点
        node.right = this._removeNode(node.right, tmpNode.data)
        return node
    } else if (data < node.data) {  //待删除节点在左子树上
        node.left = this._removeNode(node.left, data)
        return node
    } else { //待删除节点在右子树上
        node.right = this._removeNode(node.right, data)
        return node
    }
}
//删除节点
remove (data)
{
    this.root = this._removeNode(this.root, data);
}
  • 代码接收两个参数:data表示待删除的节点的值,node表示当前递归调用的节点。
  • 如果待删除节点的值等于当前节点的值(data == node.data),则进入条件判断。
  • 如果当前节点是叶子节点(即没有左子节点和右子节点),则将其置为null,表示删除该节点。
  • 如果当前节点只有左子节点而没有右子节点,则返回其左子节点,将其作为当前节点的父节点的新子节点。
  • 如果当前节点只有右子节点而没有左子节点,则返回其右子节点,将其作为当前节点的父节点的新子节点。
  • 如果当前节点既有左子节点又有右子节点,则需要找到待删除节点的右子树上的最小值来替代待删除节点。
  • 通过调用getMinNode(node.right)方法,找到右子树上的最小值所在的节点,并将其赋值给临时节点tmpNode
  • 将临时节点tmpNode的值复制到待删除节点node,相当于用右子树上的最小值替代了待删除节点。
  • 再次递归调用_removeNode()方法,传入当前节点的右子节点和临时节点的值,以删除右子树上的最小值节点。
  • 最后,返回当前节点node,表示删除操作完成。
  • 如果待删除节点的值小于当前节点的值(data < node.data),则递归调用_removeNode()方法,传入当前节点的左子节点和待删除节点的值,以在左子树上继续删除操作。
  • 如果待删除节点的值大于当前节点的值,则递归调用_removeNode()方法,传入当前节点的右子节点和待删除节点的值,以在右子树上继续删除操作。
  • 最终,整个删除操作完成后,返回当前节点node,并将其作为父节点的新子节点。

三:测试数据

    let myTree = new BSTree();

    myTree.insert(20);
    myTree.insert(13);
    myTree.insert(7);
    myTree.insert(9);
    myTree.insert(15);
    myTree.insert(14);
    myTree.insert(42);
    myTree.insert(22);
    myTree.insert(21);
    myTree.insert(24);
    myTree.insert(57);

image-20230718105531956

    console.log(myTree.getMaxNode());  
    console.log(myTree.getMinNode()); 
    myTree.remove(7)
    console.log(myTree.find(7));

image-20230718105624045

四:全部代码

    //二叉树的存储结构为
    class Node
    {
      constructor(data, left, right)
      {
        this.data = data
        this.left = left
        this.right = right
        //若有相同的元素插入节点,就放弃插入,count++
        this.count = 1
      }
    }

    //二叉排序树
    class BSTree
    {
      constructor()
      {
        this.root = null
      }

      //向二叉树插入节点
      insert (data)
      {
        let newNode = new Node(data, null, null)
        //更新根节点的值
        if (this.root === null) {
          this.root = newNode
        } else {
          //更新当前节点的值
          let currentNode = this.root
          //父节点就是空
          let parentNode = null
          while (true) {
            //更新父节点
            parentNode = currentNode
            //判断插入节点值在左子树还是右子树
            if (newNode.data < currentNode.data) {
              //更新当前节点
              currentNode = currentNode.left
              if (!currentNode) {
                parentNode.left = newNode
                break
              }
            }

            else if (newNode.data > currentNode.data) {
              currentNode = currentNode.right
              if (!currentNode) {
                parentNode.right = newNode
                break
              }
            }

            else if (newNode.data === currentNode.data) {
              //如果相同数据 count++ 不做处理
              currentNode.count++
              break
            }
          }
        }
      }

      //获取最小值
      getMinNode (node = this.root)
      {
        let currentNode = node
        while (currentNode.left) {
          currentNode = currentNode.left
        }
        return currentNode
      }

      //获取最大值
      getMaxNode (node = this.root)
      {
        let currentNode = node
        while (currentNode.right) {
          currentNode = currentNode.right
        }
        return currentNode
      }

      //寻找给定数据的节点
      find (data)
      {
        let currentNode = this.root
        while (currentNode) {
          if (currentNode.data == data) {
            return currentNode
          } else if (currentNode.data < data) {
            currentNode = currentNode.right
          } else {
            currentNode = currentNode.left
          }
        }
        return null
      }
      //删除节点,实例中不应调用
      _removeNode (node, data)
      {
        if (node === null) {
          return null
        }
        //找到要删除的节点的了 
        if (data === node.data) {
          //分三种情况
          //1. 要删除的节点为叶子结点 
          if (node.left === null && node.right === null) {
            return null
          }

          //2. 没有左子节点的节点
          if (node.left === null) return node.right

          //   没有右子节点的节点
          if (node.right === null) return node.left


          //3.有两个子节点的节点
          //找到待删除节点的右子树的最小值赋值给临时节点tmpNode
          //将tmpNode赋值给node 就说明用右子树的最小值来代替待删除节点
          let tmpNode = this.getMinNode(node.right)
          //tmpNode赋值给待删除节点
          node.data = tmpNode.data
          //删除临时节点
          node.right = this._removeNode(node.right, tmpNode.data)
          return node
        } else if (data < node.data) {  //待删除节点在左子树上
          node.left = this._removeNode(node.left, data)
          return node
        } else { //待删除节点在右子树上
          node.right = this._removeNode(node.right, data)
          return node
        }
      }
      //删除节点
      remove (data)
      {
        this.root = this._removeNode(this.root, data);
      }
    }

    let myTree = new BSTree();

    myTree.insert(20);
    myTree.insert(13);
    myTree.insert(7);
    myTree.insert(9);
    myTree.insert(15);
    myTree.insert(14);
    myTree.insert(42);
    myTree.insert(22);
    myTree.insert(21);
    myTree.insert(24);
    myTree.insert(57);
    console.log(myTree);

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

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

相关文章

Consul的容器服务更新与发现

Consul的容器服务更新与发现 一&#xff1a;Docker consul的容器服务更新与发现&#xff08;1&#xff09;什么是服务注册与发现&#xff08;2&#xff09;什么是consulconsul提供的一些关键特性&#xff1a; 二&#xff1a;consul 部署consul服务器1. 建立 Consul 服务2. 查看…

一文带你全面理解向量数据库

近些年来&#xff0c;向量数据库引起业界的广泛关注&#xff0c;一个相关事实是许多向量数据库初创公司在短期内就筹集到数百万美元的资金。 你很可能已经听说过向量数据库&#xff0c;但也许直到现在才真正关心向量数据库——至少&#xff0c;我想这就是你现在阅读本文的原因…

美国过境签证的办理流程和注意事项

美国过境签证是一种临时签证&#xff0c;允许你在前往其他国家的途中经过美国。这对于很多旅行者来说是一个便捷的选择&#xff0c;但在申请和办理过程中也需要注意一些要点。 首先&#xff0c;申请美国过境签证需要前往美国驻中国大使馆或领事馆递交申请。以下是办理美国过境签…

四磺酸酞菁锌,61586-86-5,Zn(II) Phthalocyanine tetrasulfonic acid,广泛用于染色

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ 四磺酸酞菁锌试剂 | 基础知识概述&#xff08;部分&#xff09;: 中文名称&#xff1a;四磺酸酞菁锌 英文名称&#xff1a;Zn(II) Phthalocyanine tetrasulfonic acid CAS号&#xff1a;61586-86-5 分子式&#xff1a;C32H…

图数据库实践 - 如何将图数据库应用于网络安全

网络化办公为企业创造便捷的同时&#xff0c;也带来了数据泄露的威胁。根据IBM Security最新发布的年度《数据泄露成本报告》显示&#xff0c;2023全球数据泄露的平均成本达到445万美元&#xff0c;创该报告有史以来以来最高记录&#xff0c;也较过去3年均值增长了15%。同一时期…

2.3 HLSL常用函数

一、函数介绍 函数图像参考网站&#xff1a;Graphtoy 1.基本数学运算 函数 含义 示例图 min(a,b) 返回a、b中较小的数值 mul(a,b) 两数相乘用于矩阵计算 max(a,b) 返回a、b中较大的数值 abs(a) 返回a的绝对值 round(x) 返回与x最近的整数 sqrt(x) 返回x的…

QT【day4】

chat_QT服务器端&#xff1a; //.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTcpServer> //服务器类 #include<QTcpSocket> //客户端类 #include<QMessageBox> //对话框类 #include<QList> //链表容器 #inc…

美团/华为/字节/滴滴等大厂真实面试面经

一、美团测试开发面经 一面&#xff0c;1小时 自我介绍 自已觉得最好的项目&#xff1f;主要做了什么&#xff1f;遇到的最大困难&#xff1f; 浏览器输入网址后发生了什么&#xff1f; 三次握手和四次挥手 http和https的区别 https的加密过程 知道哪些排序算法 快排的…

现在入行软测=49年入国军?三句话,让面试官再掏2K

还有一个月就步入金九银十&#xff0c;很多软测人吐槽因为疫情&#xff0c;公司都在裁员&#xff0c;别说跳槽涨薪&#xff0c;能保住现在的工作就不错了。 但也有那么一批人&#xff0c;凭借自己口才与实力拿到年薪近50W的offer。面试是初见1小时就要相互了解优缺点的过程&am…

工作10年的老码农手把手教你如何3分钟看懂IT技术管理!速收藏!

老陈是谁&#xff1f; 一个码龄十年的老码农&#xff0c;从刚毕业开始被代码折磨的死去活来&#xff0c;到公司里“被迫”成为多线技术栈的“工程师”&#xff0c;这几年又从IT技术转向做IT管理。 基本可以说从一个坑跳到了另一个坑&#xff0c;虽然坑多水深&#xff0c;但是…

【Golang】Golang进阶系列教程--Golang中文件目录操作的实现

文章目录 一、文件二、文件目录三、文件目录操作3.1、读取文件3.1.1、方法一 (file.Read())3.1.2、方法二 (bufio读取文件)3.1.3、方法三 (ioutil 读取方法) 3.2、写入文件3.2.1、方法一3.2.2、方法二3.2.3、方法三 (ioutil写入文件) 3.3、复制文件3.3.1、方法一3.3.2、方法二 …

AI人工智能未来在哪里?2023年新兴产业人工智能有哪些就业前景?

AI人工智能未来在哪里&#xff1f;2023年新兴产业人工智能有哪些就业前景&#xff1f; 随着科技的不断发展&#xff0c;人工智能技术也在不断地进步。在数字化时代&#xff0c;人工智能技术已经渗透到了我们生活的各个方面。2023年为止中国产业80%已经实现半自动化&#xff0c;…

element 级联 父传子

html代码例子 父组件 <el-cascaderstyle"width: 100%"change"unitIdChange":options"unitOptions"filterablev-model"formInline.unitId":props"unitProps"/></el-form-item>//改变级联传值到这个组件里面<r…

自动化测试如何做?搭建接口自动化框架从0到1实战(超细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 传统软件测试行业…

C#时间轴曲线图形编辑器开发1-基本功能

目录 一、前言 1、简介 2、开发过程 3、工程下载链接 二、基本功能实现 1、绘图面板创建 &#xff08;1&#xff09;界面布置 &#xff08;2&#xff09;显示面板代码 &#xff08;3&#xff09; 面板水平方向、竖直方向移动功能实现 &#xff08;4&#xff09;面板放…

kotlin 编写一个简单的天气预报app(三)

使用eventbus替换broadcast 将从Broadcast切换到EventBus有以下几个好处&#xff1a; 解耦性&#xff1a;通过使用EventBus&#xff0c;您可以实现组件之间的解耦。传统的Broadcast机制需要发送方和接收方明确知道对方的存在&#xff0c;并且需要在代码中设置Intent过滤器和广…

Mnist分类与气温预测任务

目录 传统机器学习与深度学习的特征工程特征向量pytorch实现minist代码解析归一化损失函数计算图Mnist分类获取Mnist数据集&#xff0c;预处理&#xff0c;输出一张图像面向工具包编程使用TensorDataset和DataLoader来简化数据预处理计算验证集准确率 气温预测回归构建神经网络…

网页版五子棋项目演示

项目源码&#xff1a;五子棋游戏 演示使用的用户名&#xff1a;zyz 密码:123 注册页面&#xff1a; 登录页面&#xff1a; 游戏大厅页面&#xff1a; 未匹配&#xff1a; 匹配中&#xff1a; 游戏房间页面&#xff1a; 对方落子&#xff1a; 己方落子&#xff1a; 对…

5.6 Java递归讲解

5.6 Java递归讲解 A方法调用B方法&#xff0c;我们很容易理解递归就是&#xff1a;A方法调用A方法&#xff01;就是自己调用自己利用递归可以实现通过简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解&#xff0…

Redis的五大数据类型介绍

、简介 Redis的五大数据类型也称五大数据对象&#xff1b;前面介绍过6大数据结构&#xff0c;Redis并没有直接使用这些结构来实现键值对数据库&#xff0c;而是使用这些结构构建了一个对象系统redisObject&#xff1b;这个对象系统包含了五大数据对象&#xff0c;字符串对象&am…