二叉查找树

二叉查找树(Binary Search Tree)

二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树。顾名思义,二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。

二叉查找树的特殊结构

二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值(如下图所示)。


image.png

1. 二叉查找树的查找操作

先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。


image.png

2. 二叉查找树的插入操作

二叉查找树的插入过程有点类似查找操作。新插入的数据一般都是在叶子节点上,所以我们只需要从根节点开始,依次比较要插入的数据和节点的大小关系。

如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。


image.png

3. 二叉查找树的删除操作

二叉查找树的查找、插入操作都比较简单易懂,但是它的删除操作就比较复杂了 。针对要删除节点的子节点个数的不同,我们需要分三种情况来处理。

第一种情况是,如果要删除的节点没有子节点,我们只需要直接将父节点中,指向要删除节点的指针置为 null。比如图中的删除节点 55。

第二种情况是,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),我们只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。比如图中的删除节点 13。

第三种情况是,如果要删除的节点有两个子节点,这就比较复杂了。我们需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。比如图中的删除节点 18。


image.png

代码demo

   // 节点
    class Node {
      constructor(key) {
        this.key = key;
        this.left = null;
        this.right = null;
      }
    }

    // 二叉搜索树
    class BinarySearchTree {
      constructor() {
        this.root = null;
        this.count = 0;
      }
      // 向树中插入一个新的键
      insert(key) {
        if (this.root === null) {
          this.root = new Node(key);
        } else {
          this.insertNode(this.root, key);
        }
        this.count += 1;
      }
      insertNode(node, key) {
        if (key < node.key) {
          if (node.left === null) {
            node.left = new Node(key);
          } else {
            this.insertNode(node.left, key);
          }
        } else {
          if (node.right === null) {
            node.right = new Node(key);
          } else {
            this.insertNode(node.right, key);
          }
        }
      }

      // 在树中查找一个键。如果节点存在,则返回true;如果不存在,则返回false
      search(key){
        let bool = false;
        let node = this.root;
        while(node != null){
          if(key === node.key){
            bool = true;
            break;
          }
          else if(key < node.key){
            node = node.left;
          }else{
            node = node.right
          }
        }
        return bool;
      }

      // 从树中删除一个key
      remove(key) {
        // node 指向要删除的节点,初始化指向根节点
        let node = this.root;
        // pNode 记录的是node的父节点
        let pNode = null;
        while (node !== null && node.key !== key) {
          pNode = node;
          if (key < node.key) {
            node = node.left;
          } else {
            node = node.right;
          }
        }
        // 没有找到
        if (node === null) {
          return;
        }

        // 第1种:删除的节点左节点和右节点都没有
        if (node.left === null && node.right === null) {
          if (key < pNode.key) {
            pNode.left = null;
          } else {
            pNode.right = null;
          }
        } else if (node.left === null || node.right === null) { // 第2种:删除的节点有左节点或者右节点一个节点
          const child = node.left || node.right;
          if (key < pNode.key) {
            pNode.left = child;
          } else {
            pNode.right = child;
          }
        } else { //第3种:删除的节点左节点和右节点都有
          let minNode = node.right;
          let pMinNode = node;
          while (minNode.left !== null) {
            pMinNode = minNode;
            minNode = minNode.left;
          }
          // 将minNode的数据替换到node中
          node.key = minNode.key;
          // 删除minNode
          pMinNode.left = minNode.right;
        }

        this.count -= 1;
      }

      // 前序遍历
      preOrder(callback) {
        this.preOrderNode(this.root, callback);
      }
      preOrderNode(node, callback) {
        if (node !== null) {
          callback(node.key);
          this.preOrderNode(node.left, callback);
          this.preOrderNode(node.right, callback);
        }
      }

      // 中序遍历
      inOrder(callback) {
        this.inOrderNode(this.root, callback);
      }
      inOrderNode(node, callback) {
        if (node !== null) {
          this.inOrderNode(node.left, callback);
          callback(node.key);
          this.inOrderNode(node.right, callback);
        }
      }

      // 后序遍历
      postOrder(callback) {
        this.postOrderNode(this.root, callback);
      }
      postOrderNode(node, callback) {
        if (node !== null) {
          this.postOrderNode(node.left, callback);
          this.postOrderNode(node.right, callback);
          callback(node.key);
        }
      }
    }

    let tree = new BinarySearchTree();
    tree.insert(11);
    tree.insert(7);
    tree.insert(15);
    tree.insert(5);
    tree.insert(3);
    tree.insert(9);
    tree.insert(8);
    tree.insert(10);
    tree.insert(13);
    tree.insert(12);
    tree.insert(14);
    tree.insert(20);
    tree.insert(18);
    tree.insert(25);
    tree.insert(6);

    console.log(tree.remove(14))
    console.log(tree.remove(13))
    // console.log(tree.remove(11))

    console.log(tree)

    tree.inOrder((key) => console.log(key));
    // 查找
    console.log(tree.search(15))

PS:笔记内容来自 极客时间 数据结构与算法之美

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容