美文网首页我爱编程
JS实现二叉排序树

JS实现二叉排序树

作者: ceido | 来源:发表于2018-05-21 20:51 被阅读0次

    来自慕课网免费教程:https://www.imooc.com/video/15751
    做做笔记。

    最近才发现原来这是《学习JavaScript数据结构和算法》上的代码,这是JavaScript中比较好的一本算法书吧,浅显易懂。但是作者的一些书写习惯有点想不通,类中所有的属性包括方法都作为私有属性,而应该作为私有属性的却又作为局部变量,形成闭包。后面发现快速排序也有点变种,跟以前学过的不太一样。
    但总之也值得一看吧。

    之前也实现过链表,现在一回忆都忘了,再找当时学习时的代码文件,都不知道去哪里了,看来记录总是好的。

    二叉排序树,又叫二叉搜索树,又叫二叉查找树。

    其特点就是左子节点一定小于父节点,而右子节点一定大于父节点。
    如图就是一个二叉排序树:


    image.png
    (1)创建二叉排序树,直接贴代码了:
        function BinaryTree() {
            var Node = function(key) {    
                this.key = key;
                this.left = null;
                this.right = null;
            };
    
            var root = null;
    
            this.getRoot = function() {      // 从root开始通过left和right跟其他节点连接一起,得到root就相当于得到整颗树了
                return root;
            }
    
            this.insert = function(key) {
                var newNode = new Node(key);
    
                if (root === null) {
                    root = newNode;
                } else {
                    insertNode(root, newNode);
                }
            };
            var insertNode = function(node, newNode) {     // 构建二叉排序树
                if (newNode.key < node.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);
                    }
                }
            };
        }
    
        var nodes = [8, 3, 10, 1, 6, 14, 4, 7, 13];
        var binaryTree = new BinaryTree();
    
        nodes.forEach((key) => {
            binaryTree.insert(key);
        });
        console.log(binaryTree.getRoot());
    

    (2)遍历

    二叉排序树的中序遍历是有序且升序的!

            this.inOrderTraverse = function(callback) {   
                inOrderTraverseNode(root, callback);
            };
            var inOrderTraverseNode = function(node, callback) {   // 中序遍历
                if (node !== null) {
                    inOrderTraverseNode(node.left, callback);   //  遍历完左子树
                    callback(node.key);                         // 
                    inOrderTraverseNode(node.right, callback);
                }
            };
    
        var callback = function(key) {
            console.log(key);
        }
    
        console.log('中序遍历:');
        binaryTree.inOrderTraverse(callback);
    
    image.png

    前序,后序都一样,之前自己不知道怎么用代码写,敲了一遍发现挺简单的,换个位置而已。直接贴代码:

            this.preOrderTraverse = function(callback) {
                preOrderTraverseNode(root, callback)
            };
            this.postOrderTraverse = function(callback) {
                postOrderTraverseNode(root, callback)
            };
    
            var preOrderTraverseNode = function(node, callback) {
                if (node !== null) {
                    callback(node.key);
                    preOrderTraverseNode(node.left, callback);
                    preOrderTraverseNode(node.right, callback);
                }
            };
            var postOrderTraverseNode = function(node, callback) {
                if (node !== null) {
                    postOrderTraverseNode(node.left, callback);
                    postOrderTraverseNode(node.right, callback);
                    callback(node.key);
                }
            };
    

    前序遍历对拷贝一个二叉树来说,比新创建一个二叉树的成本要低很多,前序遍历效率要高很多。后序遍历则可用于文件路径系统中。

    输出一下:

        console.log('前序遍历:');
        binaryTree.preOrderTraverse(callback);
    
        console.log('后序遍历:');
        binaryTree.postOrderTraverse(callback);
    
    image.png image.png

    (3)最大值、最小值节点:

    可以看到二叉排序树的最小值、最大值,就是最左边、最右边的节点。

            this.getMin = function() {
                return getMinNode(root);
            };
            this.getMax = function() {
                return getMaxNode(root);
            };
    
            var getMinNode = function(node) {
                if (node) {
                    while (node.left) {
                        node = node.left;
                    }
    
                    return node.key;
                }
                return null;
            };
    
            var getMaxNode = function(node) {
                if (node) {
                    while (node.right) {
                        node = node.right;
                    }
    
                    return node.key;
                }
                return null;
            };
    

    (4)查找节点是否存在,返回boolean值

            this.search = function(key) {
                return searchNode(root, key);
            };
    
            var searchNode = function(node, key) {
                if (node === null) {
                    return false;
                }
                if (key < node.key) {
                    return searchNode(node.left, key);    // 注意递归中要return回来
                } else if (key > node.key) {
                    return searchNode(node.right, key);
                } else {
                    return true;
                }
            };
    

    (5)删除节点

    删除节点就有点麻烦了,可分为三种情况:
    ①删除叶子节点:如果是叶子节点,直接删除

    ②删除有左子树或右子树其一的节点:如果只有右子树,直接用右子树取代该节点。如果只有左子树,直接用左子树取代该节点。由于二叉排序树的特点,你可以画一下(比如删除10或14),会发现结果还是符合二叉排序树的。

    ③删除左右子树都有的节点

            this.remove = function(key) {
                root = removeNode(root, key);
                //removeNode(root, key);
            };
    
            var removeNode = function(node, key) {   // 最终返回的是root元素
                if (node === null) {
                    return null;
                }
                if (key < node.key) {
                    node.left = removeNode(node.left, key); // 递归的最后一层返回是null,而此时node即为删除的节点的父节点。递归的最前一层返回的是root
                    return node;
    
                    // removeNode(node.left, key);
                    //return
                } else if (key > node.key) {
                    node.right = removeNode(node.right, key);
                    //removeNode(node.right, key);
                    return node;
                    //return
                } else {
                    if (node.left === null && node.right === null) { // 如果是叶子节点,直接删除
                        //console.log(root.left.left)
                        node = null;
                        //root.left.left = null
                        //console.log(node === root.left.left)
                        
                        return node;
                        //return
                    }
    
                    if (node.left === null) {  // 如果只有右子树,直接用右子树取代该节点
                        node = node.right;
                        return node;
                        //return
                    } else if (node.right === null) {
                        node = node.left;
                        return node;
                        //return
                    }
    
                    // 当有两个孩子时
                    var aux = findMinNode(node.right);
                    node.key = aux.key;
                    node.right = removeNode(node.right, aux.key);
                    return node;
                }
            };
            var findMinNode = function(node) {
                if (node) {
                    while (node.left) {
                        node = node.left;
                    }
    
                    return node;   // 与前面不同的是返回了node而不是其值。
                }
                return null;
            };
    

    具体记录一下,对于删除左右子树都存在的节点的情况,比如删除节点3。

    其操作是:找到右子树所有节点中的最小节点(即4),然后将3赋值为该节点值:


    image.png

    然后再把最小节点删除,结果如下:


    image.png

    这样操作后就删除掉节点3了,且还是保持为二叉排序树。

    看了这门课还是学到不少东西的,这对于刷编程题会很有用,后面练习还没看完。看完再记录一下。

    另外,对于对于删除节点时,我开始想:不是对树的操作吗,为什么还要把节点一层一层的返回,直接操作完return结束不就好了?(可以看到我注释掉的代码)。

            node = null;                        // 删除后不return node会失败 
            //root.left.left = null             // 删除成功
            //console.log(node === root.left.left)     // true
    

    可是我改完后发现不成功。才发现自己的理解还有待加深,趁这个机会再好好总结了一下关于JS的引用:https://www.jianshu.com/p/bf043921ff58

    另外,必须得刷一下题目才能巩固:
    中序遍历
    前序遍历
    后序遍历

    二叉搜索树结点最小距离
    翻转二叉树
    二叉树的最小深度
    二叉树的最大深度

    加一个层次遍历:

            this.levelOrder = function(callback) {
                var arr = [];
                levelOrderNode(root, callback, arr);        
            }
    
            var levelOrderNode = function(node, callback, arr) {
                
                if (node === null) {
                    return;
                }
                arr.push(node);
                
                while (arr.length > 0) {
                    var node = arr.shift();
                    callback(node.key);
                    if (node.left) {
                        arr.push(node.left);
                    }
                    if (node.right) {
                        arr.push(node.right);
                    }
                }
    
            }
    

    相关文章

      网友评论

        本文标题:JS实现二叉排序树

        本文链接:https://www.haomeiwen.com/subject/fyfqjftx.html