算法篇 - 二叉搜索树

前言

在前端的工做当中,二叉搜索树不怎么常见,虽然没有快排、冒泡、去重、二分、希尔等算法常见,可是它的做用,在某些特定的场景下,是很是重要的。javascript

目前es6的使用场景比较多,因此我准备能用es6的地方就用es6去实现。
复制代码

正文

上图是我从网上找的,最主要是让你们看一下,树长啥样。前端

在这里简单的介绍一下有关二叉搜索树的术语,在后续讨论中将会提到。一棵树最上面的节点称为 根节点,若是一个节点下面链接两个节点,那么该节点称为父节点,它下面的节点称为子 节点。一个节点最多只有 0 - 2 个子节点。没有任何子节点的节点称为叶子节点。vue

什么是二叉搜索树

二叉搜索树就是一种非线性的数据结构,通常是用来存储具备层级关系的数据,好比,咱们要作一个可视化的文件系统,相似于云盘网页版,它有区分不一样的文件夹,每一个文件夹下面都有不一样的内容,它们每一个文件夹,是没有任何的关系的,惟一的关系,就是同一层级的文件夹,都有一个父级文件夹。java

和非线性数据结构相反的,是线性数据结构,线性数据结构其实在平时就比较常见了,前端经常使用的线性数据结构有:栈、队列、数组。node

为何要用二叉搜索树

选择二叉搜索树而不是那些基本的数据结构,是由于在二叉搜索树上进行查找很是快,为二叉搜索树添加或删除元素 也很是快。es6

二叉搜索树搜索特色

  • 二叉搜索树的每一个节点的子节点不容许超过两个;
  • 每一个节点的左节点永远都比本身小,右节点反之;

二叉搜索树的实现

二叉搜索树的实现功能包括添加节点删除节点查询节点(最大值、最小值,某一个指定值)算法

二叉搜索树的遍历方式包括中序遍历先序遍历后序遍历数组

建立二叉搜索树节点和节点的操做类

建立一个节点

在建立一个节点时,咱们要记住二叉搜索树节点的特性,子节点不容许超过两个:数据结构

class Node {
    constructor({data = null, left = null, right = null}){
        this._data = data
        this._left = left
        this._right = right
    }
    show(){
        return this._data
    }
}
复制代码

这样,建立了一个节点,默认为 null,有 left 和 right 两个节点,添加一个 show 方法,显示当前节点的数据;post

建立一个操做节点的类

如今,咱们有了一个节点,知道了这个节点有哪些属性,接下来,咱们建立一个能够操做这些节点的类:

class BinarySearchTree {
    constructor(){
        this._root = null
    }
}
复制代码

这里咱们建立了一个根节点,这个根节点就是二叉搜索树最底层的那个根,接下来,咱们先添加一个添加节点的方法。

添加节点

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  insert(data) {
    let node = new Node({
      data: data
    })
    if (this._root == null) {
      this._root = node
    }
    else { 
      let current, parent
      current = this._root
      while (true) { 
        parent = current
        if (data < current.data) {
          current = current.left
          if (current == null) {
            parent.left = node
            break
          }
        }
        else { 
          current = current.right
          if (current == null) { 
            parent.right = node
            break
          }
        }
      }
    }
  }
}
复制代码

这里,咱们添加了一个 insert 方法,这个方法就是用来添加节点的;

初始化先检查根节点 root 是不是 null,若是是 null,那说明不存在根节点,以当前添加的节点为根节点;

若是存在根节点的话,那么接下来值的对比目标,初始化以根节点作对比目标,确认大于根节点,仍是小于根节点;

若是小于的话,那么就下次用根节点的左节点 left 来作对比,固然,若是左节点是空的,直接把当前要添加的节点看成左节点 left 就ok了。

若是大于的话,和小于反之;

而后咱们如今,开始添加值,测试一些方法:

let bst = new BinarySearchTree()
bst.insert(2)
bst.insert(1)
bst.insert(3)
复制代码

咱们添加了一个根节点,是2,而后添加了两个叶子的节点 1 和 3 ,1 就在根节点 2 的左侧, 3 就在根节点 2 的右侧,接下来咱们加一个删除节点的方法。

删除节点

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  remove(data) {
    this._root = this._removeNode(this._root, data)
  }
  _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
      }
      var tempNode = this._getSmallest(node._right)
      node._data = tempNode._data
      node._right = this._removeNode(node._right, tempNode._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
    }
  }
  _getSmallest(node) {
    if (node._left == null) {
      return node
    } else {
      return this._getSmallest(node._left)
    }
  }
}
复制代码

这个是用来删除某一个节点的方法。

这个方法最后返回的是一个新的 node 节点,若是第一次调用的时候 node 是 null 的话,说明这个二叉搜索树是空的,不存在任何值,直接返回空;

若是当前要删除的值,是当前节点的值,那么去检查它的左右节点是否存在值,若是都不存在,直接返回 null,由于说明当前的节点下面没有子级节点,就不须要对子级节点作处理了,直接删除当前节点就好;

若是左节点是 null 的,那么直接用当前节点的右节点替换当前节点;

反之,右节点是 null 的,那么直接用当前节点的左节点替换当前节点;

若是,当前要删除的节点,左右节点都存在的话,那么就去遍历要删除节点的右侧节点,若是右侧节点不存在它的左节点,那么直接返回右侧节点,若是存在,一直递归,找到最底层的一个左侧节点返回结果;

这里其实简要归纳一下,就是去找删除节点右节点树当中的最小值,来代替当前被删除节点的位置
复制代码

若是当前要删除的值,比当前节点的值小,就递归调用,一直找到当前值并删除为止;

若是当前要删除的值,比当前节点的值大,与上面反之;

接下来,咱们添加一个查找的方法:

查找当前节点

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  find(data) {
    let current = this._root
    while (current != null) {
      if (current._data == data) {
        return true
      } else if (data < current._data) {
        current = current._left
      } else {
        current = current._right
      }
    }
    return false
  }
}
复制代码

这个方法是用来查找一个值的,若是当前查询的值小于当前节点的值,那就从当前的节点左侧去查询;

反之,若是大于,就直接去右侧查询;

若是这个值始终查询不到,那么就返回 false,不然就是 true。

查找最小值

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  getMin() {
    let current = this._root
    while (current._left != null) {
      current = current._left
    }
    return current._data
  }
}
复制代码

这是一个查询最小值的方法,因为二叉搜索树数据结构的特殊性,左侧的值永远是最小的,因此一直查询到低,找到最底层的左节点返回就能够了。

查询最大值

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  getMax() {
    let current = this._root
    while (current._right != null) {
      current = current._right
    }
    return current._data
  }
}
复制代码

和查询最小值相反。

操做二叉搜索树节点的方式都有了,接下来该遍历二叉搜索树了。

遍历二叉搜索树

把二叉搜索树的数据遍历一遍,用中序先序后序遍历,代码量比较少,代码也不是伪代码都是我本身测过的,结果就不截图了,把执行顺序的流程图发一下,结果你们感兴趣本身跑一下就行了:

中序遍历

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  inOrder(node) {
    if (node != null) {
      this.inOrder(node._left)
      console.log(node.show())
      this.inOrder(node._right)
    }
  }
}
复制代码

先遍历左节点,在遍历根节点,最后遍历右节点,步骤以下:

先序遍历

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  preOrder(node) {
    if (node != null) {
      console.log(node.show())
      this.inOrder(node._left)
      this.inOrder(node._right)
    }
  }
}
复制代码

这是先序遍历,先遍历根节点,在遍历左节点,最后遍历右节点,步骤以下:

后序遍历

class BinarySearchTree {
  constructor() {
    this._root = null
  }
  postOrder(node) {
    if (node != null) {
      this.inOrder(node._left)
      this.inOrder(node._right)
      console.log(node.show())
    }
  }
}
复制代码

这是后序遍历,先遍历叶子节点,从左到右,最后遍历根节点,步骤以下:

结束语

到这里,二叉搜索树讲解的就差很少了,你们对于哪里有疑问,随时欢迎评论。

原本这一章是应该写 vue 源码解析(实例化前)的最终章的,可是涉及到的东西比较多,并且我想把前几章的总结一下写到最后一章。

因此这一章就先写一章有关算法的文章,谢谢你们支持🙏

相关文章
相关标签/搜索