第六讲 树

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

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

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

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

二叉树

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

如: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=nullthis.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)

输出结果转化之后:

树的遍历

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

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

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

中序遍历:从最小到最大

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

  • 每次递归前,应检查传入的节点是否为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;// 找到右侧子树的最小节点。__nodelet __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()在判断前执行。如果是在判断后执行,会是什么结果呢?

转载于:https://www.cnblogs.com/djtao/p/10720962.html

数据结构第六讲: 树相关推荐

  1. 数据结构思维 第六章 树的遍历

    第六章 树的遍历 原文:Chapter 6 Tree traversal 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 本章将介绍一个 Web 搜索引擎,我们将在本书其余部分开 ...

  2. 趣学数据结构--第六章:树

    趣学数据结构---->第六章:树 二叉树 线索二叉树 树的应用 二叉树的深度 二叉树的叶子数 二叉树的结点数 三元组创建二叉树 遍历序列还原树 哈夫曼树 二叉树 二叉树的创建(询问创建以及补空创 ...

  3. 《大话数据结构》第六章 树

    文章目录 第六章 树 树的定义 结点的分类 树的抽象数据类型 树的存储结构 双亲表示法 孩子表示法 孩子兄弟表示法 二叉树 特殊二叉树 斜树 满二叉树 完全二叉树 二叉树的性质 二叉树的存储结构 遍历 ...

  4. 《大话数据结构》读书笔记-树

    写在前面:本文仅供个人学习使用.<大话数据结构>通俗易懂,适合整体做笔记输出,构建体系.并且文中很多图片来源于该书,如有侵权,请联系删除. 文章目录 6.2 树的定义 6.2.1 结点分类 ...

  5. Mysql存储引擎之TokuDB以及它的数据结构Fractal tree(分形树)

    在目前的Mysql数据库中,使用最广泛的是innodb存储引擎.innodb确实是个很不错的存储引擎,就连高性能Mysql里都说了,如果不是有什么很特别的要求,innodb就是最好的选择.当然,这偏文 ...

  6. AcWing基础算法课Level-2 第六讲 贪心

    AcWing基础算法课Level-2 第六讲 贪心 区间问题 AcWing 905. 区间选点1751人打卡 AcWing 908. 最大不相交区间数量1613人打卡 AcWing 906. 区间分组 ...

  7. 数据结构(三)---树

    数据结构(三)-树 树的定义 树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合.把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根在上,而叶在下的. 有一个 ...

  8. JUC第六讲:ThreadLocal/InheritableThreadLocal详解/TTL-MDC日志上下文实践

    本文是JUC第六讲:ThreadLocal/InheritableThreadLocal详解.ThreadLocal无论在项目开发还是面试中都会经常碰到,本文就 ThreadLocal 的使用.主要方 ...

  9. 天勤2022数据结构(六)图

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 天勤2022数据结构(六)图 前言 一.基础算法 例题 Prim算法 Kruskal算法 Dijkstra算法 Floyd算法 拓扑排 ...

最新文章

  1. Windows 7搜索功能讲解
  2. eclipse java 程序调试_使用 Eclipse 调试 Java 程序的 10 个技巧
  3. cgi web 调用多次启动_简单说明CGI和动态请求是什么
  4. logic回归是一种线性回归
  5. MySQL——分页查询
  6. Asterisk配置文件说明
  7. Linux/Ubuntu: 命令行任务(To-Do List)管理 task - A command line todo manager
  8. python创意编程是什么_Python趣味创意编程
  9. VMware ESXi 环境备份与还原处理案例
  10. TensorFlow官方发布剪枝优化工具:参数减少80%,精度几乎不变
  11. core部署iis的 调试net_ASP.NET Core环境变量和启动设置的配置教程
  12. 廖雪峰Git学习 | 笔记二:修改以及版本回退
  13. java开源商城系统|代码哥如何用java小程序直播商城搭建直播带货平台技术解决方案
  14. python windows自动化 爬虫_python自动化之爬虫原理及简单案例
  15. 锦鱼课堂:跪着赚快钱,一年20W你愿意吗?
  16. 通过Hook进行游戏的全局加速
  17. Java QQ群成员资料
  18. 图片批处理工具:ImageSize Mac
  19. 2020年诺贝尔生理学或医学奖揭晓:由发现丙肝病毒的两位美国及一位英国科学家共同获奖...
  20. 基于JAVA体育城场地预定系统后台计算机毕业设计源码+数据库+lw文档+系统+部署

热门文章

  1. opencv的色彩空间类型转换之色彩空间基础
  2. 【专利权-思维导图】知识产权之专利权全部知识
  3. 2020年网络安全漏洞态势报告-整体漏洞趋势
  4. 美股因美联储决定加速缩减规模大涨,令投资者有信心对抗通胀
  5. 华为软挑赛2023-初赛笔记
  6. mmcv之Registry类解读
  7. 为邮件创建完美的HTML正文
  8. HBase基础: 表设计思路
  9. 根据身份证统计各年龄段的性别人数
  10. 丛林生存法则——狼与鹿