JavaScript浅尝二叉搜索树(BST)

163 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

在当前这个大环境下(越来越卷🐶),我最近又开始重温算法和数据结构了。

说到数据结构,大家应该多少听到过二叉树平衡二叉树, 二叉搜索树等等。本篇会使用JavaScript实现一个基本的二叉搜索树

二叉搜索树

二叉搜索树(Binary Search Tree,BST)是一种二叉树,其中每个结点最多有两个子结点且具有二叉搜索树性质:左子树上所有结点的值均小于它的根结点的值以及右子树上所有结点的值均大于它的根结点的值 ——visualgo image.png

特点及方法

image.png

代码实现 

设计构思

以一个class类的形式来承载一个数据结构是一个好的设计.并且定义好一个树的接口类型.大致如下

// 树节点的接口类型
interface TreeNode {
  value: number;
  left: TreeNode | null,
  right: TreeNode | null
}
class BST {
    root: TreeNode | null;
    constructor() {}
    // 生成节点
    createNode(){}
    // 插入节点
    insertNode(){}
    // 查找节点
    search(){}
    // 移除节点
    remove(){}
    // ...
}

排列出 一个基本的BST所具有的功能, 接下来就一一来实现吧~。

生成节点

设计思路:在实例化时如果想生成根节点,可以传入根节点的value, 便于生产根节点.

  constructor(root: number) {
    this.root = root ? this.createNode(root) : null;
  }
 /**
  * @description 生产TreeNode
  * @param value 
  */
  private createNode(value: number): TreeNode {
    return {
      value,
      left: null,
      right: null
    };
  }

插入节点

设计思路:

  1. 如果没有根节点,则再生成跟节点
  2. 根据上面的提到的特性规则,插入节点 动画演示

2_.gif

 /**
  * @description 插入节点
  * @param {number} newValue 
  */
  public insertNode(newValue: number) {
    let head = this.root;
    if (!head) {
      this.root = this.createNode(newValue);
      return;
    }
    let headValue = head.value;
    let done = false;
    while(!done) {
      if (newValue > headValue) {
        if (!head.right) {
          head.right = this.createNode(newValue);
          done = true;
        } else {
          headValue = head.right?.value as number;
          head = head.right;
        }
      } else if (newValue < headValue) {
        if (!head.left) {
          head.left = this.createNode(newValue);
          done = true;
        } else {
          headValue = head.left?.value as number;
          head = head.left;
        }
      } else {
        console.warn("已存在节点,无需重复添加")
        done = true;
      }
    }
  }

查找节点

设计思路: 遍历节点树找到对应节点, 动画和上面插入类似

  /**
   * @description 查找节点
   * @param {number} targetValue 
   */
  public search(targetValue: number): undefined | TreeNode {
    let head = this.root;
    if (!head) {
      return undefined;
    }
    let done = false;
    let findItem = undefined;
    while(!done) {
      if (head.value === targetValue) {
        findItem = head;
        done = true;
      } else {
        if (head.left) {
          head = head.left;
        } else if (head.right) {
          head = head.right;
        } else {
          done = true;
        }
      }
    }
    return findItem;
  }

遍历节点

  1. 前序遍历 遍历顺序: 中 - 左 - 右

动画演示

1_.gif


  /**
   * @description 前序遍历
   * @param {Function}  cb 
   */
  public preOrderTraverse(cb: (item: TreeNode["value"]) => void) {
    this.preOrderTraverseNode(this.root, cb);
  }
  private preOrderTraverseNode(node: TreeNode | null | undefined, cb: Function) {
    if (node === null || node === undefined) return;
    cb(node.value);
    this.preOrderTraverseNode(node.left, cb);
    this.preOrderTraverseNode(node.right, cb);
  }
  1. 中序遍历 遍历顺序: 左 - 中 - 右

动画演示

2_.gif

  /**
   * @description 中序遍历
   * @param {Function}  cb 
   */
  public inOrderTraverse(cb: (item: TreeNode["value"]) => void) {
    this.inOrderTraverseNode(this.root, cb);
  }
  private inOrderTraverseNode(node: TreeNode | null | undefined, cb: Function) {
    if (node === null || node === undefined) return;
    this.inOrderTraverseNode(node.left, cb);
    cb(node.value);
    this.inOrderTraverseNode(node.right, cb);
  }

3.后序遍历 遍历顺序: 左 - 右 - 中

动画演示

visualgo 还没支持...大家脑补一下啦.... image.png

  /**
   * @description 后序遍历
   * @param {Function}  cb 
   */
  public postOrderTraverse(cb: (item: TreeNode["value"]) => void) {
    this.postOrderTraverseNode(this.root, cb);
  }
  private postOrderTraverseNode(node: TreeNode | null | undefined, cb: Function) {
    if (node === null || node === undefined) return;
    this.postOrderTraverseNode(node.left, cb);
    this.postOrderTraverseNode(node.right, cb);
    cb(node.value);
  }

移除节点

思路:

考虑的方面:

  1. 只是一个叶节点
  2. 只有一个子节点
  3. 两个子节点

情况比较多,还是动画演示一下吧

1_.gif

  /**
   * @description 移除节点
   * @param {number} targetValue 
   */
  public remove(targetValue: number) {
    this.root = this.removeNode(this.root, targetValue);
  }
  private removeNode(node: TreeNode | null, value: number) {
    if(node === null) {
      return null
    }
    if (value < node.value) {
      node.left = this.removeNode((node.left as TreeNode), value)
      return node;
    } else if(value > node.value) {
      node.right = this.removeNode((node.right as TreeNode), value)
      return node;
    } else {
      // 一个叶节点
      if(node.left === null && node.right === null) {
          node = null;
          return node;
      }
      // 只有一个子节点的节点
      if(node.left === null) {
        node = node.right;
        return node;
      } else if(node.right === null) {
        node = node.left;
        return node;
      }
      /** 有两个字节点的情况 */
      const nextMinNode = this.getMinNode(node.right);
      node.value = nextMinNode.value;
      node.right = this.removeNode(node.right, nextMinNode.value);
      return node;
    }
  }
  private getMinNode(node: TreeNode) {
    while(node && node.left !== null) {
        node = node.left;
    }
    return node
  }

思考总结

一个基本的二叉搜索树就简单的实现了,虽然前端目前用到的地方可能还不是很多,但是二叉树,BST, AVL等所涉及的算法,数据结构的思维还是很重要的。

源码地址,源码更详细一些,感兴趣的小伙伴看下~

更多推荐

关于斐波那契数列,你会几种呀!

如何用JS实现单链表?