zoukankan      html  css  js  c++  java
  • 数据结构第六讲: 树

    第六讲 树

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

    img

    在明代世系表这棵树中,所有的皇帝都被称为节点。朱元璋称为根节点。后代是皇帝的节点,称为内部节点。没有子元素的节点比如明思宗朱由检称为外部节点叶节点。朱棣及其后代节点称为朱元璋的子树

    以明宣宗朱瞻基为例子,他拥有三个祖先节点。因此他的深度为3。

    树的高度取决于节点深度的最大值。根节点出于第0层。朱棣属于第二层。以此类推。整个世系表中,他的高度为12。

    二叉树

    二叉树最多只能有·2个子节点。

    img

    如:B为A的左侧子节点。E为A的右侧子节点。

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

    功能的逐步实现

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

    还记得链表(linkList)吗,可以通过指针来表示节点之间的关系。同时,还可以用对象来实现这个二叉树,

    实现以下功能:

    • insert(key):在树中插入一个新键
    • search(key):在树中查找一个键,存在则返回true,否则为false
    • inOderTraverse,preOderTraverse,postOderTraverse:中序/先序/后序遍历所有节点
    • min/max:返回树中最小/最大的键值
    • remove:从树中移除某个键。

    插入节点

    // 树
    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)
    

    输出结果转化之后:

    树的遍历

    遍历一棵树,应当从顶层,左层还是右层开始?

    遍历的方法需要以访问者模式(回调函数)体现。

    树方法最常用的就是递归。那么应如何设计?

    中序遍历:从最小到最大

    中序遍历的顺序是“从最小到最大”。

    img

    • 每次递归前,应检查传入的节点是否为null。这是递归停止的条件。
    • 调用相同的函数访问左侧子节点。直到找到最小的。
    • 访问完了,再访问最近的右侧节点,直到不可访问。
        // 中序遍历
        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)的父节点(parentNode)和这个节点的子节点(_root.left、_root.right)判断:

      • 如果_root没有子节点,那么直接把父节点对应的side值设为null

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

      • 如果两个都有:

        • 找到_root右边子树的最小节点_node,然后令parentNode的指针指向这个节点
        • _node的父节点删除指向_node的指针。
    _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()在判断前执行。如果是在判断后执行,会是什么结果呢?

  • 相关阅读:
    Eclipse中添加MyEclipse插件
    用GWT开发的HelloGWT程序
    GWT module 'xxx' may need to be (re)compiled解决办法
    GWTDesigner_v5.1.0破解码
    Firefox火狐广告过滤插件Adblock Plus过滤规则包[中文维护小组]
    工程师们,不要想一辈子靠技术混饭吃
    PHP生成类似类似优酷、腾讯视频等其他视频链的ID
    (外挂破解)Cheat Engine(内存修改工具)V6.2中文版软件介绍
    ucos-ii核心算法分析(转)
    Websocket,ProtoBuffer,Hightlight,JSON 等,最近遇到的一些知识点小结
  • 原文地址:https://www.cnblogs.com/djtao/p/10720962.html
Copyright © 2011-2022 走看看