javascript实现数据结构: 树和森林
树的3种常用链表结构
1 双亲表示法(顺序存储结构)
优点:parent(tree, x)操作可以在常量时间内实现
缺点:求结点的孩子时需要遍历整个结构
用一组连续的存储空间来存储树的结点,同时在每个结点中附加一个指示器(整数域) ,用以指示双亲结点的位置(下标值) 。
图所示是一棵树及其双亲表示的存储结构。这种存储结构利用了任一结点的父结点唯一的性质。可以方便地直接找到任一结点的父结点,但求结点的子结点时需要扫描整个数组。
代码实现:
// 1.双亲表示法
// 优点:parent(tree, x)操作可以在常量时间内实现
// 缺点:求结点的孩子时需要遍历整个结构
function ParentTree() {
this.nodes = [];
}
ParentTree.prototype = {
constructor: ParentTree,
getDepth: function () {
var maxDepth = 0; for (var i = 0; i < this.nodes.length; i++) {
var dep = 0;
for (var j = i; j >= 0; j = this.nodes[i].parent) dep++;
if (dep > maxDepth) maxDepth = dep;
} return maxDepth;
}
};
function ParentTreeNode(data, parent) {
// type: ParentTree
this.data = data || null;
// 双亲位置域 {Number}
this.parent = parent || 0;
}
var pt = new ParentTree();
pt.nodes.push(new ParentTreeNode('R', -1));
pt.nodes.push(new ParentTreeNode('A', 0));
pt.nodes.push(new ParentTreeNode('B', 0));
pt.nodes.push(new ParentTreeNode('C', 0));
pt.nodes.push(new ParentTreeNode('D', 1));
pt.nodes.push(new ParentTreeNode('E', 1));
pt.nodes.push(new ParentTreeNode('F', 3));
pt.nodes.push(new ParentTreeNode('G', 6));
pt.nodes.push(new ParentTreeNode('H', 6));
pt.nodes.push(new ParentTreeNode('I', 6));
2 孩子链表表示法
树中每个结点有多个指针域,每个指针指向其一棵子树的根结点。有两种结点结构。
⑴ 定长结点结构
指针域的数目就是树的度。
其特点是:链表结构简单,但指针域的浪费明显。结点结构如下图(a)所示。在一棵有n个结点,度为k的树中必有n(k-1)+1空指针域。
⑵ 不定长结点结构
树中每个结点的指针域数量不同,是该结点的度,如图(b) 所示。没有多余的指针域,但操作不便。
⑶ 复合链表结构
对于树中的每个结点,其孩子结点用带头结点的单链表表示,表结点和头结点的结构如下图所示。
n个结点的树有n个(孩子)单链表(叶子结点的孩子链表为空),而n个头结点又组成一个线性表且以顺序存储结构表示。
复合链表结构代码:
// 孩子表示法 function ChildTree() {
this.nodes = [];
}
ChildTree.prototype = {
constructor: ChildTree,
getDepth: function () {
var self = this;
return function subDepth(rootIndex) {
if (!self.nodes[rootIndex]) return 1; for (var sd = 1, p = self.nodes[rootIndex]; p; p = p.next) {
var d = subDepth(p.child);
if (d > sd) sd = d;
} return sd + 1;
}(this.data[0]);
}
};
/**
*
* @param {*} data
* @param {ChildTreeNode} firstChild 孩子链表头指针
* @constructor
*/
function ChildTreeBox(data, firstChild) {
this.data = data;
this.firstChild = firstChild;
}
/**
* 孩子结点
*
* @param {Number} child
* @param {ChildTreeNode} next
* @constructor
*/
function ChildTreeNode(child, next) {
this.child = child;
this.next = next;
}
孩子表示法便于涉及孩子的操作的实现,但不适用于parent操作。
我们可以把双亲表示法和孩子表示法结合起来。
3 孩子兄弟表示法(二叉树表示法)
以二叉链表作为树的存储结构。
两个指针域:分别指向结点的第一个子结点和下一个兄弟结点。结点类型定义如下:
// 孩子兄弟表示法(二叉树表示法)
// 可增设一个parent域实现parent操作
function ChildSiblingTree(data) {
this.data = data || null;
this.firstChild = null;
this.nextSibling = null;
}
ChildSiblingTree.prototype = {
// 输出孩子兄弟链表表示的树的各边
print: function print() {
for (var child = this.firstChild; child; child = child.nextSibling) {
console.log('%c %c', this.data, child.data);
print.call(child);
}
},
// 求孩子兄弟链表表示的树的叶子数目
leafCount: function leafCount() {
if (!this.firstChild) return 1;
else {
var count = 0;
for (var child = this.firstChild; child; child = child.nextSibling) {
count += leafCount.call(child);
}
return count;
}
},
// 求树的度
getDegree: function getDegree() {
if (!this.firstChild) return 0;
else {
var degree = 0;
for (var p = this.firstChild; p; p = p.nextSibling) degree++; for (p = this.firstChild; p; p = p.nextSibling) {
var d = getDegree.call(p);
if (d > degree) degree = d;
} return degree;
}
},
getDepth: function getDepth() {
if (this === global) return 0;
else {
for (var maxd = 0, p = this.firstChild; p; p = p.nextSibling) {
var d = getDepth.call(p);
if (d > maxd) maxd = d;
} return maxd + 1;
}
}
};
森林与二叉树的转换
由于二叉树和树都可用二叉链表作为存储结构,对比各自的结点结构可以看出,以二叉链表作为媒介可以导出树和二叉树之间的一个对应关系。
◆ 从物理结构来看,树和二叉树的二叉链表是相同的,只是对指针的逻辑解释不同而已。
◆ 从树的二叉链表表示的定义可知,任何一棵和树对应的二叉树,其右子树一定为空。
1 树转换成二叉树
对于一般的树,可以方便地转换成一棵唯一的二叉树与之对应。将树转换成二叉树在“孩子兄弟表示法”中已给出,其详细步骤是:
⑴ 加虚线。在树的每层按从“左至右”的顺序在兄弟结点之间加虚线相连。
⑵ 去连线。除最左的第一个子结点外,父结点与所有其它子结点的连线都去掉。
⑶ 旋转。将树顺时针旋转450,原有的实线左斜。
⑷ 整型。将旋转后树中的所有虚线改为实线,并向右斜。
2 二叉树转换成树
对于一棵转换后的二叉树,如何还原成原来的树? 其步骤是:
⑴ 加虚线。若某结点i是其父结点的左子树的根结点,则将该结点i的右子结点以及沿右子链不断地搜索所有的右子结点,将所有这些右子结点与i结点的父结点之间加虚线相连,如下图a所示。
⑵ 去连线。去掉二叉树中所有父结点与其右子结点之间的连线,如下图b所示。
⑶ 规整化。将图中各结点按层次排列且将所有的虚线变成实线,如下图c所示。
3 森林转换成二叉树
当一般的树转换成二叉树后,二叉树的右子树必为空。若把森林中的第二棵树(转换成二叉树后)的根结点作为第一棵树(二叉树)的根结点的兄弟结点,则可导出森林转换成二叉树的转换算法如下:
设F={T1, T2,⋯,Tn}是森林,则按以下规则可转换成一棵二叉树B=(root,LB,RB)
① 若n=0,则B是空树。
② 若n>0,则二叉树B的根是森林T1的根root(T1),B的左子树LB是B(T11,T12, ⋯,T1m) ,其中T11,T12, ⋯,T1m是T1的子树(转换后),而其右子树RB是从森林F’={T2, T3,⋯,Tn}转换而成的二叉树。
转换步骤:
① 将F={T1, T2,⋯,Tn} 中的每棵树转换成二叉树。
② 按给出的森林中树的次序,从最后一棵二叉树开始,每棵二叉树作为前一棵二叉树的根结点的右子树,依次类推,则第一棵树的根结点就是转换后生成的二叉树的根结点。
4 二叉树转换成森林
若B=(root,LB,RB)是一棵二叉树,则可以将其转换成由若干棵树构成的森林:F={T1, T2,⋯,Tn} 。
转换算法:
① 若B是空树,则F为空。
② 若B非空,则F中第一棵树T1的根root(T1)就是二叉树的根root, T1中根结点的子森林F1是由树B的左子树LB转换而成的森林;F中除T1外其余树组成的的森林F’={T2, T3,⋯,Tn} 是由B右子树RB转换得到的森林。
上述转换规则是递归的,可以写出其递归算法。以下给出具体的还原步骤。
① 去连线。将二叉树B的根结点与其右子结点以及沿右子结点链方向的所有右子结点的连线全部去掉,得到若干棵孤立的二叉树,每一棵就是原来森林F中的树依次对应的二叉树,如图(b)所示。
② 二叉树的还原。将各棵孤立的二叉树按二叉树还原为树的方法还原成一般的树,如图(c)所示。
树和森林的遍历
1 树的遍历
由树结构的定义可知,树的遍历有二种方法。
⑴ 先序遍历:先访问根结点,然后依次先序遍历完每棵子树。如图的树,先序遍历的次序是: ABCDEFGIJHK
⑵ 后序遍历:先依次后序遍历完每棵子树,然后访问根结点。如图的树,后序遍历的次序是: CDBFGIJHEKA
说明:
◆ 树的先序遍历实质上与将树转换成二叉树后对二叉树的先序遍历相同。
◆ 树的后序遍历实质上与将树转换成二叉树后对二叉树的中序遍历相同。
2 森林的遍历
设F={T1, T2,⋯,Tn}是森林,对F的遍历有二种方法。
⑴ 先序遍历:按先序遍历树的方式依次遍历F中的每棵树。
⑵ 中序遍历:按后序遍历树的方式依次遍历F中的每棵树。
javascript实现数据结构: 树和森林的更多相关文章
- Python入门篇-数据结构树(tree)篇
Python入门篇-数据结构树(tree)篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.树概述 1>.树的概念 非线性结构,每个元素可以有多个前躯和后继 树是n(n& ...
- javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例
栈(Stack)是限定仅在表尾进行插入或删除操作的线性表.表尾为栈顶(top),表头为栈底(bottom),不含元素的空表为空栈. 栈又称为后进先出(last in first out)的线性表. 堆 ...
- BZOJ4006 JLOI2015 管道连接(斯坦纳树生成森林)
4006: [JLOI2015]管道连接 Time Limit: 30 Sec Memory Limit: 128 MB Description 小铭铭最近进入了某情报部门,该部门正在被如何建立安全的 ...
- javascript实现数据结构:广义表
原文:javascript实现数据结构:广义表 广义表是线性表的推广.广泛用于人工智能的表处理语言Lisp,把广义表作为基本的数据结构. 广义表一般记作: LS = (a1, a2, ..., an ...
- JavaScript 版数据结构与算法(二)队列
今天,我们要讲的是数据结构与算法中的队列. 队列简介 队列是什么?队列是一种先进先出(FIFO)的数据结构.队列有什么用呢?队列通常用来描述算法或生活中的一些先进先出的场景,比如: 在图的广度优先遍历 ...
- javascript字典数据结构Dictionary实现
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat=&qu ...
- javascript实现数据结构与算法系列
1.线性表(Linear list) 线性表--简单示例及线性表的顺序表示和实现 线性表--线性链表(链式存储结构) 线性表的静态单链表存储结构 循环链表与双向链表 功能完整的线性链表 线性链表的例子 ...
- (转载)JavaScript递归查询 json 树 父子节点
在Json中知道某个属性名,想要确定该属性在Json树具体的节点,然后进行操作还是很麻烦的 可以用以下方法找到该属性所在的节点,和父节点 <!DOCTYPE html> <html ...
- ACM数据结构-树状数组
模板: int n; int tree[LEN]; int lowbit(int x){ return x&-x; } void update(int i,int d){//index,del ...
随机推荐
- 学习react教程
网址收藏: React官网,React的Github,React的中文文档 1.react是什么? React起源于Facebook的内部项目,因为该公司对市场上所有的Javascript MVC框架 ...
- 20190417 CentOS 7下安装ActiveMQ
前言 用VMware安装CentOS 7.6,创建一个新的用户lihailin9073,并使用这个用户登陆CentOS系统 安装和启动 登陆ActivieMQ官网http://activemq.apa ...
- dubbo服务暴露过程
所谓服务暴露最终做的事情:绑定网络端口,开启serversokect服务以接收外部请求 服务暴露时序图 本地暴露 远程暴露 整体总结 dubbo服务提供者暴露服务的主过程:首先 ServiceConf ...
- 我把双系统的win10抹除了现在开机只按option还是会出现双系统选择,怎么把那个win10给取消了或删除掉
找到解决方法了,按步骤来吧,准备:[打开Finder如果你在侧边设备一栏里看不到 Macintosh HD 就打开Finder设置>边栏>勾选硬盘,如果能看到请无视这一行]1. 打开终端执 ...
- jni使用javap查看java类方法签名
在Jni开发中,需要回调给java层数据,因此使用java的方法签名是必不可少的. 快速定位java方法签名的方式: java方法签名由(函数参数列表)返回值组成. cmd运行:javap -s 字节 ...
- 21.Decorator修饰器
1.类的修饰 2.方法的修饰 3.为什么修饰器不能用于函数? 4.core-decorators.js 5.使用修饰器实现自动发布事件 6.Mixin 7.Trait 8.Babel转码器的支持
- CI中使用log4php调试程序
下载log4php.我下载的版本是:apache-log4php-2.3.0-src.zip.借压缩,将压缩文件中的src/main/php/文件夹拷贝到CI的application/thrid_pa ...
- UTF-8编码
UTF-8是UNICODE的一种变长度的编码表达方式<一般UNICODE为双字节(指UCS2)>,它由Ken Thompson于1992年创建,现在已经标准化为RFC 3629.UTF-8 ...
- 百度优先收录HTTPS网站?你的网站https还在等什么
2015年5月25日,百度站长平台发布的公告,称将正式开放对HTTPS站点的收录.开始优先抓取HTTPS站点.所有事情都有两面性,这个消息对于已 经到HTTPS的网站来说是个喜大普奔的好消息.对于需要 ...
- CUBA Platform —— 开源的、可靠的企业级应用开发利器
原文:CUBA Platform: An Open-Source Java Framework for Rapid Application Development 翻译:CUBA China CUBA ...