JS中的算法与数据结构之二叉查找树(Binary Sort Tree)实例详解


Posted in Javascript onAugust 16, 2019

本文实例讲述了JS中的算法与数据结构之二叉查找树(Binary Sort Tree)。分享给大家供大家参考,具体如下:

二叉查找树(Binary Sort Tree)

我们之前所学到的列表,栈等都是一种线性的数据结构,今天我们将学习计算机中经常用到的一种非线性的数据结构——树(Tree),由于其存储的所有元素之间具有明显的层次特性,因此常被用来存储具有层级关系的数据,比如文件系统中的文件;也会被用来存储有序列表等。

在树结构中,每一个结点只有一个前件,称为父结点,没有前件的结点只有一个,称为树的根结点,简称树的(root)。每一个结点可以有多个后件,称为该结点的子结点。没有后件的结点称为叶子结点。一个结点所拥有的子结点的个数称为该结点的度,所有结点中最大的度称为树的度。树的最大层次称为树的深度。

二叉树

二叉树是一种特殊的树,它的子节点个数不超过两个,且分别称为该结点的左子树(left subtree)与右子树(right subtree),二叉树常被用作二叉查找树和二叉堆或是二叉排序树(BST)。

JS中的算法与数据结构之二叉查找树(Binary Sort Tree)实例详解 
二叉树

按一定的规则和顺序走遍二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次,这个操作被称为树的遍历,是对树的一种最基本的运算。由于二叉树是非线性结构,因此,树的遍历实质上是将二叉树的各个结点转换成为一个线性序列来表示。

按照根节点访问的顺序不同,树的遍历分为以下三种:前序遍历,中序遍历,后序遍历;

前序遍历:根节点->左子树->右子树

JS中的算法与数据结构之二叉查找树(Binary Sort Tree)实例详解 
先序遍历

中序遍历:左子树->根节点->右子树

JS中的算法与数据结构之二叉查找树(Binary Sort Tree)实例详解 
中序遍历

后序遍历:左子树->右子树->根节点

JS中的算法与数据结构之二叉查找树(Binary Sort Tree)实例详解 
后序遍历

因此我们可以得之上面二叉树的遍历结果如下:前序遍历:ABDEFGC 中序遍历:DEBGFAC 后序遍历:EDGFBCA

二叉查找树(BST)

实际应用中,树的每个节点都会有一个与之相关的值对应,有时候会被称为。因此,我们在构建二叉查找树的时候,确定子节点非常的重要,通常将相对较小的值保存在左节点中,较大的值保存在右节点中,这就使得查找的效率非常高,因此被广泛使用。

二叉查找树的实现

根据上面的知识,我们了解到二叉树实际上是由多个节点组成,因此我们首先就要定义一个Node类,用于存放树的节点,其构造与前面的链表类似。Node类的定义如下:

//节点定义

function Node (data , left , right) {
  this.data = data;    // 数据
  this.left = left;    // 左节点
  this.right = right;   // 右节点
  this.show = show;    // 显示节点数据
}

function show(){
  return this.data;
}

Node对象既保存了数据,也保存了它的左节点和右节点的链接,其中 show 方法用来显示当前保存在节点中数据。

现在我们可以创建一个类,用来表示二叉查找数(BST),我们初始化类只包含一个成员,一个表示二叉查找树根节点的 Node 对象,初始化为 null , 表示创建一个空节点。

//二叉查找树(BST)的类

function BST(){
  this.root = null;      // 根节点
  this.insert = insert;    // 插入节点
  this.preOrder = preOrder;  // 先序遍历
  this.inOrder = inOrder;   // 中序遍历
  this.postOrder = postOrder; // 后序遍历
  this.find = find;      // 查找节点
  this.getMin = getMin;    // 查找最小值
  this.getMax = getMax;    // 查找最大值
  this.remove = remove;    // 删除节点
}

现在,我们需要为我们的类添加方法。

首先就是 insert 方法,向树中添加一个新节点,我们一起来看看这个方法;

insert:向树中添加新节点

因为添加节点会涉及到插入位置的问题,必须将其放到正确的位置上,才能保证树的正确性,整个过程较为复杂,我们一起来梳理一下:

首先要添加新的节点,首先需要创建一个Node对象,将数据传入该对象。

其次要检查当前的BST树是否有根节点,如果没有,那么表示是一棵新数,该节点就为该树的根节点,那么插入这个过程就结束了;否则,就要继续进行下一步了。

如果待插入节点不是根节点,那么就必须对BST进行遍历,找到合适的位置。该过程类似遍历链表,用一个变量存储当前节点,一层一层遍历BST,算法如下:

  1. 设值当前节点为根节点
  2. 如果待插入节点保存的数据小于当前节点,则新节点为原节点的左节点,反之,执行第4步
  3. 如果当前节点的左节点为null,就将新节点放到这个位置,退出循环;反之,继续执行下一次循环
  4. 设置新节点为原节点的右节点
  5. 如果当前节点的右节点为null,就将新节点放到这个位置,退出循环;反之,继续执行下一次循环

这样,就能保证每次添加的新节点能够放到正确的位置上,具体实现如下;

//插入新节点

function insert(data) {
  var n = new Node( data , null , null );
  if( this.root == null ){
    this.root = n;
  }else{
    var current = this.root;
    var parent;
    while( true ){
      parent = current;
      if( data < current.data ){
        current = current.left;
        if( current == null ){
          parent.left = n ;
          break;
        }
      }else{
        current = current.right;
        if( current == null ){
          parent.right = n;
          break;
        }
      }
    }
  }
}

现在BST类已初步成型,但操作还仅仅限于插入节点,我们需要有遍历BST的能力,上面我们也提到了是三种遍历方式。其中中序遍历是最容易实现的,我们需要升序的方法访问树中的所有节点,先访问左子树,在访问根节点,最后是右子树,我们采用递归来实现!

inOrder:中序遍历

// 中序遍历
 
function inOrder (node) {
  if( !(node == null )){
    inOrder( node.left );
    console.debug( node.show() + ' ');
    inOrder( node.right );
  }
}

怎么样,了解了原理,实现起来还是蛮简单的~

我们用一段代码来测试一下我们所写的中序遍历:

var nums = new BST();
//插入数据
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);

上述插入数据后,会形成如下的二叉树

JS中的算法与数据结构之二叉查找树(Binary Sort Tree)实例详解 
BST

中序遍历结果如下:

//中序遍历
console.log("Inorder traversal: ");
inOrder(nums.root);

// Inorder traversal:
// 3 16 22 23 37 45 99

preOrder:先序遍历

有了中序遍历的基础,相信先序遍历的实现你已经想出来,怎么样?看看对吗?

//先序遍历

function preOrder( node ) {
  if( !(node == null )){
    console.log( node.show() + ' ');
    preOrder( node.left );
    preOrder( node.right );
  } 
}

怎么样,看起来是不是和中序遍历差不多,唯一的区别就是 if 语句中代码的执行顺序,中序遍历中 show 方法放在两个递归调用之间,先序遍历则放在递归调用之前。

先序遍历结果如下:

// 先序遍历

console.log("Preorder traversal: ");
preOrder(nums.root);

// Preorder traversal:
// 23 16 3 22 45 37 99

postOrder:后序遍历

后序遍历的实现和前面的基本相同,将 show 方法放在递归调用之后执行即可

//后序遍历
 
function postOrder ( node ) {
  if( !(node == null ) ){
    postOrder( node.left );
    postOrder( node.right );
    console.log( node.show() + ' ');
  }
}

后序遍历结果如下:

// 后序遍历

console.log("Postorder traversal: ");
postOrder(nums.root);

// Postorder traversal:
// 3 22 16 37 99 45 23

二叉查找树的查找运算

对于BST通常有一下三种的查找类型:

  1. 查找给定值
  2. 查找最大值
  3. 查找最小值

我们接下来一起来讨论三种查找的方式的实现。

要查找BST中的最小值和最大值是非常简单的。因为较小的值总是在左子节点上,要想查找BST中的最小值,只需遍历左子树,直到找到最后一个节点即可。同理,查找最大值,只需遍历右子树,直到找到最后一个节点即可。

getMin:查找最小值

遍历左子树,直到左子树的某个节点的 left 为 null 时,该节点保存的即为最小值

//查找最小值

function getMin( ) {
  var current = this.root;
  while ( !( current.left == null ) ){
    current = current.left;
  }
  return current.show();
}

getMax:查找最大值

遍历右子树,直到右子树的某个节点的 right 为 null 时,该节点保存的即为最大值

//查找最大值
 
function getMax () {
  var current = this.root;
  while ( !( current.right == null ) ) {
    current = current.right;
  }
  return current.show();
}

我们还是利用前面构建的树来测试:

// 最小值
console.log('min:' + nums.getMin() );    // min : 3

//最大值
console.log('max:' + nums.getMax() );    // max : 99

在BST上查找给定值,需要比较给定值和当前节点保存的值的大小,通过比较,就能确定给定值在不在当前节点,根据BST的特点,qu接下来是向左还是向右遍历;

//查找给定值

function find ( data ) {
  var current = this.root;
  while ( current != null ){
    if( current.data == data ){
      return current;
    }else if( current.data < data ){
      current = current.right;
    }else{
      current = current.left;
    }
  }
  return null;
}

如果找到给定值,该方法返回保存该值的节点,反之返回null;

//查找不存在的值
console.log('find:' + nums.find(66));    // find : null

//查找存在的值
console.log('find:' + nums.find(99) );   // find : [object Object]

二叉查找树的删除运算

从BST中删除节点的操作最为复杂,其复杂程度取决于删除的节点位置。如果待删除的节点没有子节点,那么非常简单。如果删除包含左子节点或者右子节点,就变得稍微有些复杂。如果删除包含两个节点的节点最为复杂。

我们采用递归方法,来完成复杂的删除操作,我们定义 remove() 和 removeNode() 两个方法;算法思想如下:

  1. 首先判断当前节点是否包含待删除的数据,如果包含,则删除该节点;如果不包含,则比较当前节点上的数据和待删除树的的大小关系。如果待删除的数据小于当前节点的数据,则移至当前节点的左子节点继续比较;如果大于,则移至当前节点的右子节点继续比较。
  2. 如果待删除节点是叶子节点(没有子节点),那么只需要将从父节点指向它的链接指向变为null;
  3. 如果待删除节点含有一个子节点,那么原本指向它的节点需要做调整,使其指向它的子节点;
  4. 最后,如果待删除节点包含两个子节点,可以选择查找待删除节点左子树上的最大值或者查找其右子树上的最小值,这里我们选择后一种。

因此,我们需要一个查找树上最小值的方法,后面会用它找到最小值创建一个临时节点,将临时节点上的值复制到待删除节点,然后再删除临时节点;

我们上面说会用到两个方法,其中 remove 方法只是简单的接收待删除数据,调用 removeNode 删除它,主要工作在 removeNode 中完成,定义如下:

//删除操作

function remove( data ) {
  removeNode( this.root , data);
}

//查找最小值

function getSmallest(node) {
  if (node.left == null) {
    return node;
  }
  else {
    return getSmallest(node.left);
  }
}

//删除节点
function removeNode( node , data ) {
  if( node == null ) {
    return null;
  }
  if(data == node.data) {
    // 没有子节点的节点
    if(node.left == null && node.right == null) {
      return null;
    }
    // 没有左子节点的节点
    if(node.left == null) {
      return node.right;
    }
    // 没有右子节点的节点
    if(node.right == null) {
      return node.left;
    }
    // 有2个子节点的节点
    var tempNode = getSmallest(node.right);
    node.data = tempNode.data;
    node.right = removeNode(node.right,tempNode.data);
    return node;

  }else if(data < node.data) {
    node.left = removeNode( node.left,data);
    return node;
  }else {
    node.right = removeNode( node.right,data);
    return node;
  }
}

现在我们来删除节点试试。

//删除根节点
nums.remove(23);

inOrder(nums.root);

// 3 16 22 37 45 99

成功了!现在,我们的BST算是完整了。

我们现在已经可以利用js是实现一个简单的BST了,实际中树的使用会很广泛,希望大家能多去了解了解树的数据结构,多动手实践,相信你会有不少的收获!

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
jquery创建一个新的节点对象(自定义结构/内容)的好方法
Jan 21 Javascript
JavaScript组件焦点与页内锚点间传值的方法
Feb 02 Javascript
js实现点击图片将图片地址复制到粘贴板的方法
Feb 16 Javascript
jQuery实现预加载图片的方法
Mar 17 Javascript
jQuery代码实现发展历程时间轴特效
Jul 30 Javascript
JS碰撞运动实现方法详解
Dec 15 Javascript
jQuery的ajax中使用FormData实现页面无刷新上传功能
Jan 16 Javascript
Vue监听数据对象变化源码
Mar 09 Javascript
浅谈vue项目如何打包扔向服务器
May 08 Javascript
分享5个好用的javascript文件上传插件
Sep 16 Javascript
Node登录权限验证token验证实现的方法示例
May 25 Javascript
解决vue-pdf查看pdf文件及打印乱码的问题
Nov 04 Javascript
微信小程序webview组件交互,内联h5页面并网页实现微信支付实现解析
Aug 16 #Javascript
JS中的算法与数据结构之常见排序(Sort)算法详解
Aug 16 #Javascript
jQuery zTree插件使用简单教程
Aug 16 #jQuery
微信小程序 如何保持登录状态
Aug 16 #Javascript
JS事件流与事件处理程序实例分析
Aug 16 #Javascript
javascript事件监听与事件委托实例详解
Aug 16 #Javascript
使用Vue-Awesome-Swiper实现旋转叠加轮播效果&amp;平移轮播效果
Aug 16 #Javascript
You might like
Laravel 5框架学习之Eloquent 关系
2015/04/09 PHP
解决PHP里大量数据循环时内存耗尽的方法
2015/10/10 PHP
PHP使用SOAP扩展实现WebService的方法
2016/04/01 PHP
js限制文本框为整数和货币的函数代码
2010/10/13 Javascript
前台js改变Session的值(用ajax实现)
2012/12/28 Javascript
JavaScript改变HTML元素的样式改变CSS及元素属性
2013/11/12 Javascript
AngularJS入门教程(一):静态模板
2014/12/06 Javascript
Express的路由详解
2015/12/10 Javascript
解析JavaScript中的字符串类型与字符编码支持
2016/06/24 Javascript
nodejs利用ajax实现网页无刷新上传图片实例代码
2017/06/06 NodeJs
Javascript中toFixed计算错误(依赖银行家舍入法的缺陷)解决方法
2017/08/22 Javascript
详解Ubuntu安装angular-cli遇到的坑
2018/09/08 Javascript
微信小程序身份证验证方法实现详解
2019/06/28 Javascript
Javascript类型判断相关例题及解析
2020/08/26 Javascript
[42:32]VP vs RNG 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.21.mp4
2020/07/19 DOTA
编写Python脚本使得web页面上的代码高亮显示
2015/04/24 Python
使用Python编写简单的端口扫描器的实例分享
2015/12/18 Python
pycharm 配置远程解释器的方法
2018/10/28 Python
tensorboard实现同时显示训练曲线和测试曲线
2020/01/21 Python
浅析Python迭代器的高级用法
2020/07/16 Python
在 Python 中使用 7zip 备份文件的操作
2020/12/11 Python
基于PyTorch中view的用法说明
2021/03/03 Python
canvas中普通动效与粒子动效的实现代码示例
2019/01/03 HTML / CSS
美国户外烹饪产品购物网站:Outdoor Cooking
2020/01/10 全球购物
体育学院毕业生自荐信
2013/11/03 职场文书
开业庆典答谢词
2014/01/18 职场文书
聚美优品恶搞广告词
2014/03/14 职场文书
自我鉴定书
2014/03/24 职场文书
毕业生简历自我评价范文
2014/04/09 职场文书
天地会口号
2014/06/17 职场文书
群众路线领导对照材料
2014/08/23 职场文书
研究生导师评语
2014/12/31 职场文书
虎兄虎弟观后感
2015/06/12 职场文书
个人工作总结(管理人员)范文
2019/08/13 职场文书
如何利用STAR法则制作留学文书?
2019/08/26 职场文书
解决ubuntu安装软件时,status-code=409报错的问题
2022/12/24 Servers