JavaScript实现二叉搜索树

299 阅读3分钟

二叉搜索树

二叉搜索树(BST,Binary Search Tree),也称为二叉排序树和二叉查找树。

二叉搜索树是一棵二叉树,可以为空。

如果不为空,则满足以下性质:

  • 条件 1:非空左子树的所有键值小于其根节点的键值。比如三中节点 6 的所有非空左子树的键值都小于 6;
  • 条件 2:非空右子树的所有键值大于其根节点的键值;比如三中节点 6 的所有非空右子树的键值都大于 6;
  • 条件 3:左、右子树本身也都是二叉搜索树; 总结:二叉搜索树的特点主要是较小的值总是保存在左节点上,相对较大的值总是保存在右节点上。这种特点使得二叉搜索树的查询效率非常高,这也就是二叉搜索树中“搜索”的来源。

二叉搜索树的常见操作:

  • insert(key) 向树中插入一个新的键。
  • search(key) 在树中查找一个键,如果节点存在,则返回 true;如果不存在,则返回 false
  • preOrderTraverse 通过先序遍历方式遍历所有节点。
  • inOrderTraverse 通过中序遍历方式遍历所有节点。
  • postOrderTraverse 通过后序遍历方式遍历所有节点。
  • min 返回树中最小的值/键。
  • max 返回树中最大的值/键。
  • remove(key) 从树中移除某个键。

二叉搜索树的封装

实现了插入、查找、先序遍历、中序遍历、后序遍历、返回最值、删除节点等操作。

    class Node {
        constructor(key) {
            this.key = key;
            this.left = null;
            this.right = null;
        }
    }
    class BinarySearchTree {
        constructor() {
            this.root = null;
        }
        //插入数据
        insert(key) {
            //根据传进来的key创建节点
            let newNode = new Node(key);
            //判断根节点是否为空
            if (this.root == null) {
                this.root = newNode;
            } else {
                this.insertNode(this.root, newNode);
            }
        }
        insertNode(node, newNode) {
            //判断被比较的节点和新节点的key大小
            if (newNode.key > node.key) {
                if (node.right == null) {
                    node.right = newNode;
                } else {
                    this.insertNode(node.right, newNode);
                }
            } else {
                if (node.left == null) {
                    node.left = newNode;
                } else {
                    this.insertNode(node.left, newNode);
                }
            }
        }
        //先序遍历
        preOrderTraversal() {
            this.preOrderTraversalNode(this.root);
        }
        preOrderTraversalNode(node) {
            if (node != null) {
                console.log(node.key);
                this.preOrderTraversalNode(node.left);
                this.preOrderTraversalNode(node.right);
            }
        }
        //中序遍历
        midOrderTraversal() {
            this.midOrderTraversalNode(this.root);
        }
        midOrderTraversalNode(node) {
            if (node != null) {
                this.midOrderTraversalNode(node.left);
                console.log(node.key);
                this.midOrderTraversalNode(node.right);
            }
        }
        //后序遍历
        postOrderTraversal() {
            this.postOrderTraversalNode(this.root);
        }
        postOrderTraversalNode(node) {
            if (node != null) {
                this.postOrderTraversalNode(node.left);
                this.postOrderTraversalNode(node.right);
                console.log(node.key);
            }
        }
        //查找key对应的节点
        search(key) {
            let node = this.root;
            while (node != null) {
                if (key > node.key) {
                    node = node.right;
                } else if (key < node.key) {
                    node = node.left;
                } else {
                    return true;
                }
            }
            return false;
        }
        //删除数据
        remove(key) {
            //1.1定义变量保存信息
            let parent = null;
            let current = this.root;
            let isLeftChild = true;

            //1.2查找要删除的节点
            while (current.key != key) {
                parent = current;
                if (key > current.key) {
                    current = current.right;
                    isLeftChild = false;
                } else {
                    current = current.left;
                    isLeftChild = true;
                }
                if (current == null) {
                    return false;
                }
            }

            //2.1删除叶子节点
            if (current.left == null && current.right == null) {
                if (current == this.root) {
                    this.root = null;
                } else if (isLeftChild) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
            }
            //2.2删除只有一个子节点的节点
            else if (current.left == null) {
                if (current == this.root) {
                    this.root = current.right;
                } else if (isLeftChild) {
                    parent.left = current.right;
                } else {
                    parent.right = current.right;
                }
            } else if ((current.right = null)) {
                if (current == this.root) {
                    this.root = current.left;
                } else if (isLeftChild) {
                    parent.left = current.left;
                } else {
                    parent.right = current.left;
                }
            }
            //2.3删除有两个子节点的节点
            else {
                let successor = this.getSuccessor(current);

                if (current == root) {
                    this.root = successor;
                } else if (isLeftChild) {
                    parent.left = successor;
                } else {
                    parent.right = successor;
                }
                successor.left = current.left;
            }
        }
        getSuccessor(delNode) {
            //设置变量保存临时信息
            let successor = delNode;
            let successorParent = delNode;
            let current = delNode.right;

            //查找后继节点
            while (current != null) {
                successorParent = successor;
                successor = current;
                current = current.left;
            }
            //判断找到的后继节点是否为delNode的right子节点
            if (successor != delNode.right) {
                successorParent.left = successor.right;
                successor.right = delNode.right;
            }
            return successor;
        }
    }           

删除操作的思路

  1. 查找节点
  • 查找需要删除的节点,没找到返回false

  • 找到的话分为三种情况:

    • 该节点是叶子结点
    • 该节点有一个子节点
    • 该节点有两个子节点
  1. 删除节点
  • 删除节点是叶子节点和只有一个子节点的情况比较简单,这里只分析删除有两个子节点的节点。

  • 如果我们要删除的节点有两个子节点,甚至子节点还有子节点,这种情况下我们需要从下面的子节点中找到一个节点,来替换当前的节点。

  • 这个节点怎么找呢?

    • 比current小一点点的节点,一定是current左子树的最大值。
    • 比current大一点点的节点,一定是current右子树的最小值。
  • 前驱&后继

在二叉搜索树中,这两个特别的节点,有两个特别的名字。

  • 比current小一点点的节点,称为current节点的前驱。
  • 比current大一点点的节点,称为current节点的后继。

也就是为了能够删除有两个子节点的current,要么找到它的前驱,要么找到它的后继。