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