js实现二叉搜索树(BST)

208 阅读2分钟

前言

二叉搜索树又称二叉查找树亦称为二叉排序树。其遵循着左子树的值小于其根节点的值,而根节点的值又小于其右子树的值。下面我会从零开始实现一个二叉搜索树,包括他的插入,删除,查询,先序遍历,后序遍历,中序遍历等方法。

insert(插入)

我们先实现二叉搜索树的插入方法,组装一个搜索二叉树。

// 创建节点
class Node{
  constructor(value) {
    this.val = value
    this.left = null
    this.right = null
  }
}
class BinarySearchTree {
  constructor() {
    this.root = null
  }
  // 插入方法
  insert(value) {
    if(!this.root) {  // root为null,代表第一次插入
      this.root = new Node(value)
    }else {
      this.insertNode(this.root, value)
    }
  }

  insertNode(node, value) {
    if(value < node.val) {
      if(node.left === null) {
        node.left = new Node(value)
      } else {
        this.insertNode(node.left, value) // 如果节点的左子树不为null,则需要递归节点的左子树
      }
    }else {
      if(node.val < value) {
        if(node.right === null) {
          node.right = new Node(value)
        }else {
          this.insertNode(node.right, value)  // 如果节点的右子树不为null,则需要递归节点的右子树
        }
      }
    }
    
  }
}
let bstNode = new BinarySearchTree()
bstNode.insert(3)
bstNode.insert(4)
bstNode.insert(5)
bstNode.insert(2)
bstNode.insert(9)
bstNode.insert(8)
console.log(bstNode)

以上我们就是先了二叉搜索树的插入方法,打印如下图所示:

微信图片_20221108144855.png

preOrderTraverse(先序遍历)

先序遍历遵循先遍历根节点,然后遍历左子树,最后遍历右子树的规则。

// 创建节点
class Node{
  constructor(value) {
    this.val = value
    this.left = null
    this.right = null
  }
}
class BinarySearchTree {
  constructor() {
    this.root = null
  }

  // 插入方法
  insert(value) {
    if(!this.root) {  // root为null,代表第一次插入
      this.root = new Node(value)
    }else {
      this.insertNode(this.root, value)
    }
  }
  insertNode(node, value) {
    if(value < node.val) {
      if(node.left === null) {
        node.left = new Node(value)
      } else {
        this.insertNode(node.left, value) // 如果节点的左子树不为null,则需要递归节点的左子树
      }
    }else {
      if(node.val < value) {
        if(node.right === null) {
          node.right = new Node(value)
        }else {
          this.insertNode(node.right, value)  // 如果节点的右子树不为null,则需要递归节点的右子树
        }
      }
    }
    
  }

  // 先序遍历
  preOrderTraverse(result) {
    this.preOrderTraverseNode(this.root, result)
    return result
  }
  preOrderTraverseNode(node, result) {
    if(!node) return
    result.push(node)
    this.preOrderTraverseNode(node.left, result)
    this.preOrderTraverseNode(node.right, result)
  }
}
let bstNode = new BinarySearchTree()
bstNode.insert(3)
bstNode.insert(4)
bstNode.insert(5)
bstNode.insert(2)
bstNode.insert(9)
bstNode.insert(8)
console.log(bstNode.preOrderTraverse([]))

其打印结果为:

2.png

inOrderTraverse(中序遍历)

二叉树的中序遍历遵循先遍历左子树,然后遍历其根节点,最后遍历右子树。而二叉搜索树的特点是左子树小于其根子树,而根子树又小于其右子树。及二叉搜索树的中序遍历的结果就是从小到大排列的。 增加代码:

// 中序遍历
  inOrderTraverse(result) {
    this.inOrderTraverseNode(this.root, result)
    return result
  }
  inOrderTraverseNode(node, result) {
    if(!node) return
    this.inOrderTraverseNode(node.left, result)
    result.push(node)
    this.inOrderTraverseNode(node.right, result)
  }

打印结果为:

3.png

postOrderTraverse(后序遍历)

后续遍历遵循先遍历左子树,然后遍历右子树,最后遍历其根节点

// 增加如下代码
postOrderTraverse(result) {
    this.postOrderTraverseNode(this.root, result)
    return result
  }
postOrderTraverseNode(node, result) {
if(!node) return
this.postOrderTraverseNode(node.left, result)
this.postOrderTraverseNode(node.right, result)
result.push(node)
}

以上代码打印结果为:

4.png

最小值和最大值(相当于求最左边和最右边的值)

// 增加代码

// 最小值
  min() {
    return this.minNode(this.root)
  }
  minNode(node) {
    let currentNode = node
    while(currentNode && currentNode.left) { // 找到最左边的节点
      currentNode = currentNode.left
    }
    return currentNode
  }

  // 最大值
  max() {
    return this.maxNode(this.root)
  }
  maxNode(node) {
    let currentNode = node
    while(currentNode && currentNode.right) {  // 找到最右边的节点
      currentNode = currentNode.right
    }
    return currentNode
  }

search(查询某个特定值是否存在)

// 增加代码

// 查询某个特定值是否存在
  search(value) {
    return this.searchNode(this.root, value)
  }
  searchNode(node, value) {
    if(!node) return false
    if(node.val < value) {
      return this.searchNode(node.right, value)
    }else if(node.val > value) {
      return this.searchNode(node.left, value)
    }else {
      return true
    }
  }

remove(删除一个节点)

二叉搜索树的删除相对来说会复杂一点,因为我们所要删除的节点的左右子树不一样,所要进行的操作也会不一样。

 // 删除某个节点
  remove(value) {
    this.root = this.removeNode(this.root, value)
    return this.root
  }
  removeNode(node, value) {
    if(!node) return false
    if(value < node.val) {
      node.left = this.removeNode(node.left, value)
      return node
    }else if(value > node.val) {
      node.right = this.removeNode(node.right, value)
      return node
    }else {
      // 当找到所要删除的节点之后,分情况删除

      if(!node.left && !node.right) {  // 情况一: 删除的节点左右子树都为null,直接删除该节点
        node = null
        return node
      }else if(!node.left) {  // 情况二: 删除的节点只有一个右子树,需要把该节点右子树替换该节点
        node = node.right
        return node
      }else if(!node.right) {  // 情况二: 删除的节点只有一个左子树,需要把该节点左子树替换该节点
        node = left
        return node
      }else {  // 情况三: 删除的节点拥有左子树和右子树,我们需要找到其右子树中最小的节点替换该节点
        const rightMinNode = this.minNode(node.right)
        node.val = rightMinNode.val
        node.right = this.removeNode(node.right, rightMinNode.val)  // 删除右侧最小的节点
        return node
      }
    }
  }

完整代码为:

// 创建节点
class Node{
  constructor(value) {
    this.val = value
    this.left = null
    this.right = null
  }
}
class BinarySearchTree {
  constructor() {
    this.root = null
  }

  // 插入方法
  insert(value) {
    if(!this.root) {  // root为null,代表第一次插入
      this.root = new Node(value)
    }else {
      this.insertNode(this.root, value)
    }
  }
  insertNode(node, value) {
    if(value < node.val) {
      if(node.left === null) {
        node.left = new Node(value)
      } else {
        this.insertNode(node.left, value) // 如果节点的左子树不为null,则需要递归节点的左子树
      }
    }else {
      if(node.val < value) {
        if(node.right === null) {
          node.right = new Node(value)
        }else {
          this.insertNode(node.right, value)  // 如果节点的右子树不为null,则需要递归节点的右子树
        }
      }
    }
    
  }

  // 先序遍历
  preOrderTraverse(result) {
    this.preOrderTraverseNode(this.root, result)
    return result
  }
  preOrderTraverseNode(node, result) {
    if(!node) return
    result.push(node)
    this.preOrderTraverseNode(node.left, result)
    this.preOrderTraverseNode(node.right, result)
  }

  // 中序遍历
  inOrderTraverse(result) {
    this.inOrderTraverseNode(this.root, result)
    return result
  }
  inOrderTraverseNode(node, result) {
    if(!node) return
    this.inOrderTraverseNode(node.left, result)
    result.push(node)
    this.inOrderTraverseNode(node.right, result)
  }

  // 后序遍历
  postOrderTraverse(result) {
    this.postOrderTraverseNode(this.root, result)
    return result
  }
  postOrderTraverseNode(node, result) {
    if(!node) return
    this.postOrderTraverseNode(node.left, result)
    this.postOrderTraverseNode(node.right, result)
    result.push(node)
  }

  // 最小值
  min() {
    return this.minNode(this.root)
  }
  minNode(node) {
    let currentNode = node
    while(currentNode && currentNode.left) { // 找到最左边的节点
      currentNode = currentNode.left
    }
    return currentNode
  }

  // 最大值
  max() {
    return this.maxNode(this.root)
  }
  maxNode(node) {
    let currentNode = node
    while(currentNode && currentNode.right) {  // 找到最右边的节点
      currentNode = currentNode.right
    }
    return currentNode
  }

  // 查询某个特定值是否存在
  search(value) {
    return this.searchNode(this.root, value)
  }
  searchNode(node, value) {
    if(!node) return false
    if(node.val < value) {
      return this.searchNode(node.right, value)
    }else if(node.val > value) {
      return this.searchNode(node.left, value)
    }else {
      return true
    }
  }

  // 删除某个节点
  remove(value) {
    this.root = this.removeNode(this.root, value)
    return this.root
  }
  removeNode(node, value) {
    if(!node) return false
    if(value < node.val) {
      node.left = this.removeNode(node.left, value)
      return node
    }else if(value > node.val) {
      node.right = this.removeNode(node.right, value)
      return node
    }else {
      // 当找到所要删除的节点之后,分情况删除

      if(!node.left && !node.right) {  // 情况一: 删除的节点左右子树都为null,直接删除该节点
        node = null
        return node
      }else if(!node.left) {  // 情况二: 删除的节点只有一个右子树,需要把该节点右子树替换该节点
        node = node.right
        return node
      }else if(!node.right) {  // 情况二: 删除的节点只有一个左子树,需要把该节点左子树替换该节点
        node = left
        return node
      }else {  // 情况三: 删除的节点拥有左子树和右子树,我们需要找到其右子树中最小的节点替换该节点
        const rightMinNode = this.minNode(node.right)
        node.val = rightMinNode.val
        node.right = this.removeNode(node.right, rightMinNode.val)  // 删除右侧最小的节点
        return node
      }
    }
  }
  
}
let bstNode = new BinarySearchTree()
bstNode.insert(3)
bstNode.insert(4)
bstNode.insert(5)
bstNode.insert(2)
bstNode.insert(9)
bstNode.insert(8)
console.log(bstNode.remove(8))