树的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实现数据结构: 树和森林的更多相关文章

  1. Python入门篇-数据结构树(tree)篇

    Python入门篇-数据结构树(tree)篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.树概述 1>.树的概念 非线性结构,每个元素可以有多个前躯和后继 树是n(n& ...

  2. javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例

    栈(Stack)是限定仅在表尾进行插入或删除操作的线性表.表尾为栈顶(top),表头为栈底(bottom),不含元素的空表为空栈. 栈又称为后进先出(last in first out)的线性表. 堆 ...

  3. BZOJ4006 JLOI2015 管道连接(斯坦纳树生成森林)

    4006: [JLOI2015]管道连接 Time Limit: 30 Sec Memory Limit: 128 MB Description 小铭铭最近进入了某情报部门,该部门正在被如何建立安全的 ...

  4. javascript实现数据结构:广义表

    原文:javascript实现数据结构:广义表  广义表是线性表的推广.广泛用于人工智能的表处理语言Lisp,把广义表作为基本的数据结构. 广义表一般记作: LS = (a1, a2, ..., an ...

  5. JavaScript 版数据结构与算法(二)队列

    今天,我们要讲的是数据结构与算法中的队列. 队列简介 队列是什么?队列是一种先进先出(FIFO)的数据结构.队列有什么用呢?队列通常用来描述算法或生活中的一些先进先出的场景,比如: 在图的广度优先遍历 ...

  6. javascript字典数据结构Dictionary实现

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat=&qu ...

  7. javascript实现数据结构与算法系列

    1.线性表(Linear list) 线性表--简单示例及线性表的顺序表示和实现 线性表--线性链表(链式存储结构) 线性表的静态单链表存储结构 循环链表与双向链表 功能完整的线性链表 线性链表的例子 ...

  8. (转载)JavaScript递归查询 json 树 父子节点

    在Json中知道某个属性名,想要确定该属性在Json树具体的节点,然后进行操作还是很麻烦的 可以用以下方法找到该属性所在的节点,和父节点 <!DOCTYPE html> <html ...

  9. ACM数据结构-树状数组

    模板: int n; int tree[LEN]; int lowbit(int x){ return x&-x; } void update(int i,int d){//index,del ...

随机推荐

  1. GeneXus学习笔记——入门篇

    使用GeneXus做开发做了有一段时间了 却发现一个问题(O_O)?就是除了相关的Wiki外 网上其他地方的相关资料都很少 于是乎我就想在这记录一些东西 来帮助以后会用到的人(°ー°") 那 ...

  2. C# string 特殊的引用类型

    .Net 框架程序设计(修订版)中有这样一段描述:String类型直接继承自Object,这使得它成为一个引用类型,也就是说线程上的堆栈上不会驻留有任何字符串.(译注:注意这里的“直接继承”.直接继承 ...

  3. 浏览器缓存如何控制? && 在url框中回车、F5 和 Ctrl + F5的区别是什么?

    第一部分: 浏览器缓存如何控制?   最近在做网站,但是不知道缓存是什么东西怎么能行! 如何实现HTTP缓存呢? 下面我们来一步一步的探寻实现机制把. 方案一: 无缓存   说明: 浏览器向服务器请求 ...

  4. 关系型数据库MySQL多实例

    简介 MySQL数据库是一个中小型关系型数据库管理系统,软件开发者为瑞典MySQL AB公司.在2008年1月16号被Sun公司收购后Sun公司又被oracle公司收购.目前MySQL被广泛地应用在I ...

  5. PHP之string之wordwrap()函数使用

    wordwrap (PHP 4 >= 4.0.2, PHP 5, PHP 7) wordwrap - Wraps a string to a given number of characters ...

  6. 使用go实现的lisp

    去年10月份的时候,就有这个打算了. 也是在上个月左右,抽空弄出来了个go语言实现的lisp. 当然,不能和common lisp比,函数的数量是远远不如的,也不能自己定义类型/类,同时宏系统也非常简 ...

  7. QT下载地址大全

    1. 所有Qt版本下载地址: http://download.qt.io/archive/qt/ 2. 所有Qt Creator下载地址: http://download.qt.io/archive/ ...

  8. height百分比失效

    heigh:100%失效 解决方案: 第一种 html, body { height: 100%; } 第二种 div { height: 100%; position: absolute; } 非定 ...

  9. java并发编程(7)构建自定义同步工具及条件队列

    构建自定义同步工具 一.通过轮询与休眠的方式实现简单的有界缓存 public void put(V v) throws InterruptedException { while (true) { // ...

  10. jquery 选择唯一值 选择器“”非“用法

    $(document).ready(function () { $("#addwork").hide(); $(".xi").click(function(){ ...