数据结构第六讲: 树
第六讲 树
树是一种分层数据的抽象模型。最常见的树是家谱。(图来自网络)$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
数据结构第六讲: 树相关推荐
- 数据结构思维 第六章 树的遍历
第六章 树的遍历 原文:Chapter 6 Tree traversal 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 本章将介绍一个 Web 搜索引擎,我们将在本书其余部分开 ...
- 趣学数据结构--第六章:树
趣学数据结构---->第六章:树 二叉树 线索二叉树 树的应用 二叉树的深度 二叉树的叶子数 二叉树的结点数 三元组创建二叉树 遍历序列还原树 哈夫曼树 二叉树 二叉树的创建(询问创建以及补空创 ...
- 《大话数据结构》第六章 树
文章目录 第六章 树 树的定义 结点的分类 树的抽象数据类型 树的存储结构 双亲表示法 孩子表示法 孩子兄弟表示法 二叉树 特殊二叉树 斜树 满二叉树 完全二叉树 二叉树的性质 二叉树的存储结构 遍历 ...
- 《大话数据结构》读书笔记-树
写在前面:本文仅供个人学习使用.<大话数据结构>通俗易懂,适合整体做笔记输出,构建体系.并且文中很多图片来源于该书,如有侵权,请联系删除. 文章目录 6.2 树的定义 6.2.1 结点分类 ...
- Mysql存储引擎之TokuDB以及它的数据结构Fractal tree(分形树)
在目前的Mysql数据库中,使用最广泛的是innodb存储引擎.innodb确实是个很不错的存储引擎,就连高性能Mysql里都说了,如果不是有什么很特别的要求,innodb就是最好的选择.当然,这偏文 ...
- AcWing基础算法课Level-2 第六讲 贪心
AcWing基础算法课Level-2 第六讲 贪心 区间问题 AcWing 905. 区间选点1751人打卡 AcWing 908. 最大不相交区间数量1613人打卡 AcWing 906. 区间分组 ...
- 数据结构(三)---树
数据结构(三)-树 树的定义 树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合.把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根在上,而叶在下的. 有一个 ...
- JUC第六讲:ThreadLocal/InheritableThreadLocal详解/TTL-MDC日志上下文实践
本文是JUC第六讲:ThreadLocal/InheritableThreadLocal详解.ThreadLocal无论在项目开发还是面试中都会经常碰到,本文就 ThreadLocal 的使用.主要方 ...
- 天勤2022数据结构(六)图
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 天勤2022数据结构(六)图 前言 一.基础算法 例题 Prim算法 Kruskal算法 Dijkstra算法 Floyd算法 拓扑排 ...
最新文章
- Windows 7搜索功能讲解
- eclipse java 程序调试_使用 Eclipse 调试 Java 程序的 10 个技巧
- cgi web 调用多次启动_简单说明CGI和动态请求是什么
- logic回归是一种线性回归
- MySQL——分页查询
- Asterisk配置文件说明
- Linux/Ubuntu: 命令行任务(To-Do List)管理 task - A command line todo manager
- python创意编程是什么_Python趣味创意编程
- VMware ESXi 环境备份与还原处理案例
- TensorFlow官方发布剪枝优化工具:参数减少80%,精度几乎不变
- core部署iis的 调试net_ASP.NET Core环境变量和启动设置的配置教程
- 廖雪峰Git学习 | 笔记二:修改以及版本回退
- java开源商城系统|代码哥如何用java小程序直播商城搭建直播带货平台技术解决方案
- python windows自动化 爬虫_python自动化之爬虫原理及简单案例
- 锦鱼课堂:跪着赚快钱,一年20W你愿意吗?
- 通过Hook进行游戏的全局加速
- Java QQ群成员资料
- 图片批处理工具:ImageSize Mac
- 2020年诺贝尔生理学或医学奖揭晓:由发现丙肝病毒的两位美国及一位英国科学家共同获奖...
- 基于JAVA体育城场地预定系统后台计算机毕业设计源码+数据库+lw文档+系统+部署