树的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. 网络基础 08_NAT

    1 NAT的基本概念 为什么需要NAT IPv4地址紧缺 什么是NAT NAT(Network Address Translation) 私有IPv4地址 10.0.0.0 - 10.255.255. ...

  2. Django环境搭建之hello world

    当我们想用Python来开发一个web应用时,首先要选择一个优秀的web框架,Django是个非常成熟的web开发框架,网上具有丰富的文档和学习资料,所以选择Django框架来入门web开发是个不错的 ...

  3. 【Alpha】Phylab 展示博客

    目录 Phylab Alpha 展示博客 一.团队简介 二.项目目标 2.1 典型用户 2.2 功能描述 2.3 用户量 三.项目发布与展示 3.1 新功能 3.2 修复缺陷 3.3 问题与限制 3. ...

  4. Q239 滑动窗口最大值

    给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧.你只可以看到在滑动窗口 k 内的数字.滑动窗口每次只向右移动一位. 返回滑动窗口最大值. 示例: 输入: nums ...

  5. Java学习之路(七):泛型

    泛型的概述和基本使用 作用:把类型明确的工作推前到创建对象或者调用方法的时候 泛型是一种参数化类型,把类型当做参数一样传递来明确集合的元素类型 泛型的好处 提高安全性 省去强转的麻烦 泛型的基本使用 ...

  6. Compile android source and kernel for emulator in Debian

    1.download the android source code Reference from http://source.android.com/source/downloading.html ...

  7. xcode发布ipa

    --------Xcode------- product 产品 archive 存档 (等) distribute app 分发app development 开发者 next next (等 比较漫 ...

  8. http协议--文章一

    一 原理区别 一般在浏览器中输入网址访问资源都是通过GET方式:在FORM提交中,可以通过Method 指定提交方式为GET或者POST,默认为GET提交 Http定义了与服务器交互的不同方法,最基本 ...

  9. ibatis学习笔记(完整)

    1.       Ibatis是开源软件组织Apache推出的一种轻量级的对象关系映射(ORM)框架,和Hibernate.Toplink等在java编程的对象持久化方面深受开发人员欢迎. 对象关系映 ...

  10. mac上用 adb 命令安装Android应用

    cd /Users/xxx/android-sdk-macosx/platform-tools adb install -r xxxx.apk   # -r 替换当前安装包 adb uninstall ...