JS实现二叉排序树

来自慕课网免费教程: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);
                }
            }

        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容