
树是一种分层数据的抽象模型。最常见的树是家谱。(图来自网络)

在明代世系表这棵树中,所有的皇帝都被称为节点。朱元璋称为根节点。后代是皇帝的节点,称为内部节点。没有子元素的节点比如明思宗朱由检称为外部节点或叶节点。朱棣及其后代节点称为朱元璋的子树。
以明宣宗朱瞻基为例子,他拥有三个祖先节点。因此他的深度为3。
树的高度取决于节点深度的最大值。根节点出于第0层。朱棣属于第二层。以此类推。整个世系表中,他的高度为12。
二叉树最多只能有·2个子节点。

如:B为A的左侧子节点。E为A的右侧子节点。
二叉搜索树(BST)是一种特殊的节点。左侧子节点存放比父节点小的值。右侧子节点存放大于等于父节点的值、

js创建一棵二叉树(BinarySearchTree),可以借鉴链表的思路

还记得链表(linkList)吗,可以通过指针来表示节点之间的关系。同时,还可以用对象来实现这个二叉树,
实现以下功能:
// 树
class BinarySearchTree{
    constructor(){
        this.Node=function(key){
            this.key=key;
            this.left=null;
            this.right=null;
        }
        this.root=null
        this.insertNode=this.insertNode.bind(this)
    }
    insertNode(_root,_node){
        if(_root.key>_node.key){
            if(_root.left==null){
                _root.left=_node;
            }else{
                this.insertNode(_root.left,_node);
            }
        }else{
            if(_root.right==null){
                _root.right=_node;
            }else{
                this.insertNode(_root.right,_node)
            }
        }
    }
    // 插入
    insert(key){
        let Node=this.Node;
        let node=new Node(key);
        if(this.root==null){
            this.root=node;
        }else{
            this.insertNode(this.root,node)
        }
    }
}跑一下测试用例:
let a=new BinarySearchTree();
a.insert(11)
a.insert(7)
a.insert(15)
a.insert(5)
a.insert(3)
a.insert(9)
a.insert(8)
a.insert(10)
a.insert(13)
a.insert(12)
a.insert(14)
a.insert(20)
a.insert(18)
a.insert(25)输出结果转化之后:

遍历一棵树,应当从顶层,左层还是右层开始?
遍历的方法需要以访问者模式(回调函数)体现。
树方法最常用的就是递归。那么应如何设计?
中序遍历的顺序是“从最小到最大”。

// 中序遍历
    inOrderTraverse(callback){
        // 中序遍历所需的必要方法
        const inOrderTraverseNode=(_root,_callback=()=>{})=>{
            // 从顶层开始遍历
            if(_root!==null){
                inOrderTraverseNode(_root.left,_callback);
                _callback(_root.key);
                inOrderTraverseNode(_root.right,_callback);
            }
        }
        inOrderTraverseNode(this.root,callback);
    }打印结果发现,其实这个遍历实现了树的key值从小到大排列。
a.inOrderTraverse((key)=>{console.log(key)})
// 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25先序遍历的过程:

先把左侧子节点全部访问完了,再寻找一个距此时位置(“亲缘关系”)最近的右侧节点。
preOrderTraverse(callback){
        // 中序遍历所需的必要方法
        const preOrderTraverseNode=(_root,_callback=()=>{})=>{
            // 从顶层开始遍历
            if(_root!==null){
                _callback(_root.key);
                preOrderTraverseNode(_root.left,_callback);
                preOrderTraverseNode(_root.right,_callback);
            }
        }
        preOrderTraverseNode(this.root,callback);
    }所以,所谓先序遍历就是把callback的位置提前了。

后续遍历是先访问一个树的后代节点。最后才访问本身。
那么后序遍历的方法是不是把callback放到最后执行呢?
是的。简直无脑。
// 后序遍历
    postOrderTraverse(callback){
        // 中序遍历所需的必要方法
        const postOrderTraverseNode=(_root,_callback=()=>{})=>{
            // 从顶层开始遍历
            if(_root!==null){
                postOrderTraverseNode(_root.left,_callback);
                postOrderTraverseNode(_root.right,_callback);
                _callback(_root.key);//我在后面
            }
        }
        postOrderTraverseNode(this.root,callback);
    }//是否存在
    search(_key,_root){
        if(!_root){
            _root=this.root
        }
        if(!_root){
            return false;
        }else if(_root.key==_key){
            return true;
        }
        if(_root.key>_key){
            if(_root.left==null){
                return false;
            }else{
                if(_root.left.key==_key){
                    return true
                }else{
                    return this.search(_key,_root.left)
                }
            }
        }else{
            if(_root.right==null){
                return false
            }else{
                if(_root.right.key==_key){
                    return true
                }else{
                    return this.search(_key,_root.right)
                }
            }
        }
    }// 工具函数
    find(_root,side){
        if(!_root[side]){
            return _root.key
        }else{
            return this.find(_root[side],side)
        }   
    }
    // 最大值,不断查找右边
    max(){
        return this.find(this.root,'right')
    }
    // 最小值
    min(){
        return this.find(this.root,'left')
    }会发现这是个非常轻松的事。
Bst最麻烦的方法莫过于此。
_root没有子节点,那么直接把父节点对应的side值设为null

_root拥有一个子节点,跳过这个节点,直接把父节点的指针指向这个子节点。


_root右边子树的最小节点 _node,然后令parentNode的指针指向这个节点_remove(_node,_key,parentNode,side){
        if(_key<_node.key){
            return this._remove(_node.left,_key,_node,'left')
        }else if(_key>_node.key){
            return this._remove(_node.right,_key,_node,'right')
        }else if(_node.key==_key){
            // 顶层:移除根节点
            if(!parentNode){
                this.root=null;
                return this.root;
            }else{
                if(!_node.left&&!_node.right){
                    // 删除的如果是叶节点
                    parentNode[side]=null
                }else if(_node.left&&!_node.right){
                    let tmp=_node.left;
                    parentNode[side]=tmp
                }else if(_node.right&&!_node.left){
                    let tmp=_node.right;
                    parentNode[side]=tmp
                }else{
                    let tmpRight=_node.right;
                    // 找到右侧子树的最小节点。__node
                    let __node=this.find(tmpRight,'left');
                    // 删除这个节点。
                    this._remove(tmpRight,__node.key);
                    // 重新赋值
                    parentNode[side]=__node.key;
                }
                return this.root
            }
        }
    }
    remove(key){
        if(this.search(key)){
            return this._remove(this.root,key)
        }else{
            console.log('未找到key')
            return false;
        }
    }
a.remove(15)打印结果如下

测试通过。
在实际工作生活中,比如一本书常分为第一讲,第1-1节,第2-1节...,第二讲:第2-1节...
如果后端发给你一个这样的数据:
let data = [{
    id: '1',
    children: [{
        id: `1-1`,
        children: [{
            id: '1-1-1',
            children: [{
                id: '1-1-1-1'
            },{
                id:'1-1-1-2'
            }]
        },{
            id:'1-1-2',
            children: [{
                id: '1-1-2-1'
            },{
                id:'1-1-2-2'
            }]
        }]
    },{
        id:'2',
        children:[{
            id:'2-1'
        },{
            id:'2-2',
            children:[{
                id:'2-2-1'
            },{
                id:'2-2-2',
                children: [{
                    id: '2-2-2-1'
                },{
                    id:'2-2-2-2'
                }]
            }]
        }]
    }]
}]如何扁平化如下的json对象?
const flatJson=(_data,arr)=>{
    if(!arr){
        arr=[]
    }
    for(let i=0;i<_data.length;i++){
        console.log(_data[i].id)
        arr.push(_data[i].id);
        if(_data[i].children){
            flatJson(_data[i].children,arr)
        }
    }
    return arr;
}
console.log(flatJson(data))测试用例结果通过:

可以进一步思考:这里arr.push()在判断前执行。如果是在判断后执行,会是什么结果呢?