二叉搜索树

118 阅读5分钟

定义:

二叉树的一种,专注于检索,特点是让节点按照一定的规律摆放,从而让搜索某个节点特别的高效;其中每个结点就是一个对象。除了结点中的关键字外,每个结点还包含属性left、right、p,它们分别指向结点的左孩子、右孩子和父亲结点。如果某个孩子结点和父结点不存在,则相应属性的值为null。根结点是树中唯一父结点为null的结点。

特点:

  1. 如果节点的左子树不空,则左子树上所有结点的值均小于等于它的根结点的值;
  2. 如果节点的右子树不空,则右子树上所有结点的值均大于等于它的根结点的值;
  3. 任意节点的左、右子树也分别为二叉搜索树

操作:

增:

根据二叉搜索树的定义,所以需要保证无论往这颗树上增加多少个节点,都需要保证其定义不被破坏。当遇到一个新的节点需要插入时,我们可以与根节点进行比较,如果比根节点值大就放入右子树内,如果比根节点值小就放入左子树内,使用这样的插入规则,逐层往下,总会遇到一个节点的孩子为空,将新节点设置为其新的孩子节点即可

二叉树.png

class TreeNode {
  constructor(val) {
    this.val = val
    this.left = null // 左孩子
    this.right = null // 右孩子
  }
}

class BST {
  constructor() {
    this.root = null // 根节点
  }
}



class BST {
  ...
  add(val) { // 不考虑重复值的情况
    const _helper = (node, val) => {
      if (node === null) { // 如果某个节点的孩子节点为空,创建新节点返回即可
        return new TreeNode(val)
      }
      if (node.val > val) { // 如果当前节点值比val大,新节点需要放在其左子树里
        node.left = _helper(node.left, val) // 以当前节点左孩子为起点,返回新的左孩子节点
        return node // 返回新的节点
      } else if (node.val < val) { // 同理
        node.right = _helper(node.right, val) // 同上
        return node
      }
    }
    this.root = _helper(this.root, val)
    // 从根节点开始插入val, 返回新的根节点
  }
}
查:

根据二叉搜索树的特性从根节点开始比较,还是逐层进行递归,如果根据规则最后遇到了空节点,那说明这颗树里没有这个节点。

二叉树查.png

class BST {
  ...
  contains(val) {
    const _helper = (node, val) => {
      if (node === null) { // 如果到头也没右找到
        return false
      }
      if (node.val === val) { // 正好找到
        return true
      }
      if (node.val > val) { // 如果比当前节点小
        return _helper(node.left, val) // 去左子树里找
      } else { // 如果比当前节点大
        return _helper(node.right, val) // 去右子树里找 
      }
    }
    return _helper(this.root, val) // 从根节点开始
  }
}

删:

二叉搜索树最麻烦的操作也就是删除,因为在删除了一个节点之后,原来位置的节点并不会空着,必须找到替代的节点续上才行。怎么续上而又保持二叉搜索树的定义又分为几种情况:

二叉树删.png

1.一边有孩子节点:

场景1里面的两种场景,将另一边的孩子节点续上即可。

2.两边都有孩子节点:

场景2里面也有两种处理方式,一种是在待删除节点的左子树里面找到最大的值替代(24),然后切断其链接;另一种是在待删除的右子树里面找到最小值替代(28),然后切断其链接。

为了应对场景2,我们需要找到指定节点树的最小或最大节点,然后还需要在指定节点树里删除最小或最大的节点,所以先把辅助方法写出来以便后面使用,这里采用在右子树里找最小值的方案:

class BST {
  ...
    _minimum(node) { // 返回指定节点树的最小值节点
    if (node.left === null) {
      return node
    }
    return this._minimum(node.left)
  }
  
  _removeMin(node) { // 删除指定节点树的最小节点
    if (node.left === null) {
      const rightNode = node.right
      node.right = null
      return rightNode
    }
    node.left = this._removeMin(node.left)
    return node
  }
}

根据二叉搜索树的特性,指定树的最小值一定就是沿着左边一直找,最后找到的那个非空节点;而删除指定树的最小值节点,也只需要用被删除节点的右子树续上即可。

class BST {
  ...
  remove(val) { // 删除指定值
    const _helper = (node, val) => {
      if (node === null) { // 如果到底了也没有
        return node // 直接返回空
      }
      if (node.val === val) { // 如果正好找到了
        if (node.left === null) { // 左孩子为空时,让右孩子续上
          const rightNode = node.right
          node.right = null
          return rightNode
        } else if (node.right === null) { // 右孩子为空时,让左孩子续上
          const leftNode = node.left
          node.left = null
          return leftNode
        } else { // 如果左右都右孩子
          const successor = this._minimum(node.right) // 在右子树里找最小的节点
          node.right = this._removeMin(node.right) // 并且删除右子树里最小的节点
          successor.left = node.left // 让新节点指上之前的左孩子
          successor.right = node.right // 让新节点指上之前的右孩子
          return successor // 返回新节点
        }
      } else if (node.val > val) { // 比值递归
        node.left = _helper(node.left, val)
        return node
      } else { // 比值递归即可
        node.right = _helper(node.right, val)
        return node
      }
    }
    this.root = _helper(this.root, val) // 从根节点开始,删除指定值没返回值
  }
}
中序遍历:

二叉搜索树的中序遍历是一种遍历方式,它按照从小到大的顺序遍历树中的节点,因为中序遍历会首先访问左子树、然后访问根节点、最后访问右子树。以下是中序遍历的递归和非递归实现方法:

递归实现中序遍历

class TreeNode {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

function inOrderTraversal(node) {
  if (node !== null) {
    inOrderTraversal(node.left);   // 遍历左子树
    console.log(node.value);       // 访问当前节点
    inOrderTraversal(node.right);  // 遍历右子树
  }
}

// 使用示例
const root = new TreeNode(5);
root.left = new TreeNode(3);
root.right = new TreeNode(8);
root.left.left = new TreeNode(1);
root.left.right = new TreeNode(4);
root.right.left = new TreeNode(7);
root.right.right = new TreeNode(9);

inOrderTraversal(root); // 输出:1, 3, 4, 5, 7, 8, 9

非递归实现中序遍历(使用栈)

function inOrderTraversal(root) {
  const stack = [];
  const result = [];
  let current = root;

  while (current !== null || stack.length > 0) {
    while (current !== null) {
      stack.push(current);
      current = current.left;
    }

    current = stack.pop();
    result.push(current.value);
    current = current.right;
  }

  return result;
}

// 使用示例同上

const result = inOrderTraversal(root);
console.log(result); // 输出:[1, 3, 4, 5, 7, 8, 9]