JS二叉搜索树(详细写了删除结点的相关操作)


二叉搜索树最麻烦的地方就是删除结点了,具体步骤如图。

 

function BinarySearchTree() {
    // 定义BST实例的属性
    this.root = null;
    this.count = 0;

    // 定义构造结点的函数
    function Node(key) {
        this.key = key;
        this.left = null;
        this.right = null;
    }

    // 插入
    BinarySearchTree.prototype.insert = function (key) {
        var newNode = new Node(key);

        if (this.root == null) {
            this.root = newNode;
        }
        else {
            insertNode(this.root, newNode);
        }
        this.count++;
        // 递归插入
        function insertNode(node, newNode) {
            // 向左递归
            if (node.key > newNode.key) {
                if (node.left == null) {
                    node.left = newNode;
                }
                else {
                    insertNode(node.left, newNode);
                }
            }
            // 向右递归
            else {
                if (node.right == null) {
                    node.right = newNode;
                }
                else {
                    insertNode(node.right, newNode);
                }
            }
        }
    }

    // 前序遍历
    BinarySearchTree.prototype.preOrderTraverse = function () {
        var str = "";
        pre(this.root)

        // 递归
        function pre(node) {
            if (node == null) {
                return;
            }
            else {
                str += node.key + " "       //
                pre(node.left);             //
                pre(node.right);            //
            }
        }
        console.log(str);
    }
    // 中序遍历
    BinarySearchTree.prototype.midOrderTraverse = function () {
        var str = "";
        mid(this.root)

        // 递归
        function mid(node) {
            if (node == null) {
                return;
            }
            else {
                mid(node.left);             //
                str += node.key + " "       //
                mid(node.right);            //
            }
        }
        console.log(str);
    }

    // 后序遍历
    BinarySearchTree.prototype.postOrderTraverse = function () {
        var str = "";
        post(this.root)

        // 递归
        function post(node) {
            if (node == null) {
                return;
            }
            else {
                post(node.left);             //
                post(node.right);            //
                str += node.key + " "       //
            }
        }
        console.log(str);
    }

    // 最小值
    BinarySearchTree.prototype.getMin = function () {

        return left(this.root);
        // 利用二叉搜索树的特性,进行递归查找左结点即为最小值
        function left(node) {
            var key;
            if (node == null) {
                return key;
            }
            else if(node.left = null) {
               key = left(node.left);
            }
            else {
                key = node.key;
            }
            return key;
        }
    }

    // 最大值
    BinarySearchTree.prototype.getMax = function () {

        return  right(this.root);
        // 利用二叉搜索树的特性,进行递归查找右结点即为最大值
        function right(node) {
            var key;
            if(node == null) {

            }
            else if(node.right != null) {
                key = right(node.right);
            }
            else {
                key = node.key;
            }
            return key;
        }
    }

    // 根据key查找结点, 返回node,找不到则返回undefined;
    BinarySearchTree.prototype.search = function(key) {
        return search(this.root,key);
        // 利用二叉搜索树的特性,进行递归查找
        function search(node,key) {
            var result ;
            if(node === null) {

            }
            else if(node.key == key){
                result = node;
            }
            else {
                // 向左查找
                if(key < node.key ) {
                   result = search(node.left,key);
                }
                // 向右查找
                else {  
                    result = search(node.right,key);
                }
            }
            return result;
        }

    }

    BinarySearchTree.prototype.reMove = function(key) {
        //第一步:找到要删除的结点,其中要保留父节点,当前结点,以及标记该结点是父结点的左子还是右子结点。
        var parentNode = null;      //保留父节点
        var current = this.root;    //当前结点
        var isLeftChild = true;     //标记删除结点是父结点的左子还是右子结点
        while(current.key!=key && current != null) {
            parentNode = current;   //保留父节点
            //判断往左 还是往右
            if(current.key > key) {
                current = current.left;
                isLeftChild = true;
            }
            else {
                current = current.right;
                isLeftChild = false;
            }
        }
        // 说明未找到。
        if(current == null) return false;
        console.log("当前结点:"+current.key)

        //第二步:根据找到的结点 进行不同情况的删除。
        // 1 叶节点的情况,我们只需要把叶节点设置成null即可
        if(current.left == null && current.right == null) {
            console.log("删除叶结点")
            // 1.1该节点为根结点
            if(current == this.root) {
                this.root = null;
            }
            // 1.2该节点不为根结点
            else{
                isLeftChild ? parentNode.left=null:parentNode.right=null;
            }
        } 
        // 2 节点且只有一个子节点的情况 我们只需要把父节点指向子节点即可
        // 2.1只有左子结点 
        else if (current.left != null && current.right == null) {
            console.log("删除只有1个子结点的结点")
            //判断是否为为根结点
            if(current == this.root) {
                this.root = current.left;
            }
            else {
                isLeftChild ? parentNode.left = current.left : parentNode.right = current.left;
            }
        }
        // 2.2只有右子结点
        else if(current.left == null && current.right != null) {
            console.log("删除只有1个子结点的结点")
            //判断是否为为根结点
            if(current == this.root) {
                this.root = current.right;
            }
            else {
                isLeftChild ? parentNode.left = current.right : parentNode.right = current.right;
            }
        }
        // 3 有2个子节点的情况 我们要根据替换的规律 既是在左子树中找最大的值(前驱) 或者 在右子树中找最小的值(后继) 然后与该结点替换。再将给结点设置为null
        else {
            console.log("删除有2个子结点的结点")

                // 3.2.1定义前驱结点,前驱结点的父结点
                var frontNode = current.left; // 前驱结点
                var frontParentNode = current; // 前驱结点的父结点
                
                // 3.2.2循环找前驱结点,前驱结点的父结点
                while(frontNode.right!=null) {
                    frontParentNode = frontNode;
                    frontNode = frontNode.right;
                }
                console.log("前驱结点"+frontNode.key,"前驱结点的父结点"+frontParentNode.key);
                
                // 3.2.3开始删除结点
                // 3.2.3.1因为直接找的是前驱结点,所以前驱结点的右孩子直接指向删除结点的右孩子即可。
                frontNode.right = current.right;
                // 3.2.3.2判断前驱结点是删除结点的子代还是隔代。
                // 如果是隔代 在考虑两种情况下把删除结点的左子树赋值给前驱结点。
                if(frontParentNode != current ) {
                  //第一种情况,如果前驱结点为叶子结点,那么前驱结点的左孩子指向删除结点的左子树,前驱结点的父节点右子树指向空,避免变成圆形链。
                  if(frontNode.left == null) {
                    frontNode.left = current.left;
                    frontParentNode.right = null;
                  }
                  //第二种情况,如果前驱结点不为叶子结点,根据二叉搜素树的特性,它必然有左孩子。那么前驱结点的左孩子的左孩子指向删除结点的左子树,避免变成圆形链。
                  else {
                    frontNode.left.left = current.left;
                    frontParentNode.right = null;
                  }
                }
                // 3.3 判断删除结点是否为根结点,如果是,直接把删除结点的父结点指向删除结点,同时根节点要重新指向前驱结点。
                if(current == this.root) {
                    parentNode = current;
                    this.root = frontNode;
                }
                // 3.4删除结点
                isLeftChild ? parentNode.left = frontNode : parentNode.right = frontNode;
        }
    }
}

var bst = new BinarySearchTree();

bst.insert(11);
bst.preOrderTraverse();
bst.midOrderTraverse();
bst.postOrderTraverse();
bst.reMove(11)
bst.preOrderTraverse();

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM