二叉排序树的基本操作

440 阅读3分钟
  • 二叉排序树也可以叫二叉搜索树

性质:

  • 左子树所有节点值均小于根节点
  • 右子树所有节点值均大于根节点

衍生性质:

  • 基于二叉排序数的如上性质可知,在该树的中序遍历结果中,任意一个根节点前面的节点值均小于当前根节点值,其后面的节点值均大于当前节点值,所以中序遍历的结果为升序排列的

操作:

  • 插入节点操作

    • 从树的根节点开始进行比较
    • 若新节点值较小,则递归进入左子树中进行插入操作
    • 若新节点值较大,则递归进入右子树中进行插入操作
    • 找到合适的叶子节点后,再根据叶子节点值与新节点值的大小关系,将新节点插入到该叶子节点的左右子节点中
  • 删除节点操作

    • 删除度为 0 的节点,也就是叶子节点
      • 直接删除即可
    • 删除度为 1 的节点
      • 需要将被删除节点的子节点过继给其父节点,相当于让子节点替换被删除节点的位置
    • 删除度为 2 的节点
      • 需要找到被删除节点的前驱节点或者后继节点
        • 前驱或者后继指的是在中序遍历的结果中,与该节点相邻的前一个节点或者后一个节点
        • 也就是左子树中最右边的节点,或者右子树中最左边的节点
      • 将被删除节点与前驱节点或者后继节点进行替换
      • 再删除被替换掉的前驱或者后继即可

代码示例

  • insert
    • 此方法返回插入节点之后的,这棵树的根节点
    • 如果插入节点的值 key 已存在于树中,则不插入
    • 左子树插入节点后的子树根节点赋值给 root.lefts, 右子树相同
  • erase
    • 此方法返回删除节点之后的,这棵树的根节点
    • 先根据当前节点值与需要删除的 key 值进行比较,定位删除节点在左子树还是右子树中
    • 再按照删除节点度的数量,进行对应的删除操作
  • getPredeccessor
    • 获取前驱,也就是当前节点的左子树中最右边的叶子节点
  • output
    • 打印二叉排序树的中序遍历结果,正常情况下一定是升序排列的
// 节点
class Node {
  constructor(key, left = null, right = null) {
    this.key = key;
    this.left = left;
    this.right = right;
  }
}

const getNewNode = (key) => new Node(key);

// 销毁整棵树
const clear = (root) => {
  if (!root) return;
  clear(root.left);
  clear(root.right);
  root = null;
};

// 插入节点
const insert = (root, key) => {
  if (!root) return getNewNode(key);
  if (root.key === key) return root;

  if (key < root.key) root.left = insert(root.left, key);
  else root.right = insert(root.right, key);

  return root; // 返回插完节点后的树的根节点
};

// 获取前驱
const getPredeccessor = (root) => {
  let p = root.left;
  while (p.right) p = p.right;
  return p;
};

// 删除节点
const erase = (root, key) => {
  if (!root) return root;

  if (key < root.key) root.left = erase(root.left, key);
  else if (key > root.key) root.right = erase(root.right, key);
  else {
    // 此时当前节点值与 key 值相等,需要被删除
    if (!root.left && !root.right) {
      // 若度为 0 则可直接删除
      return (root = null);
    } else if (!root.left || !root.right) {
      // 若度为 1 则需要将其子节点过继给其父节点
      return !root.left ? root.right : root.left;;
    } else {
      // 此时度为 2 则找到前驱或者后继进行替换,然后删除前驱或者后继即可
      const predeccessor = getPredeccessor(root);
      root.key = predeccessor.key;
      root.left = erase(root.left, predeccessor.key);
    }
  }
  return root;
};


const output = (root, list = []) => {
  if (!root) return;

  output(root.left, list);
  list.push(root.key);
  output(root.right, list);

  return list.join(' ');
};

let tree = null;
tree = insert(tree, 3);
console.log(output(tree)); // 3
tree = insert(tree, 1);
console.log(output(tree)); // 1 3
tree = insert(tree, 1);
console.log(output(tree)); // 1 3
tree = insert(tree, 4);
console.log(output(tree)); // 1 3 4
tree = insert(tree, 5);
console.log(output(tree)); // 1 3 4 5
tree = insert(tree, 6);
console.log(output(tree)); // 1 3 4 5 6
erase(tree, 3);
console.log(output(tree)); // 1 4 5 6
erase(tree, 4);
console.log(output(tree)); // 1 5 6

小结

  • 本文介绍了二叉排序树的基本性质与基本操作,如果有收获的话,请点个赞吧~
  • 下篇文章分享 AVL 树的相关性质与操作

“ 本文正在参加「金石计划 . 瓜分6万现金大奖」 ”