javascript实现数据结构: 树和森林

树的3种常用链表结构

1 双亲表示法(顺序存储结构)

优点:parent(tree, x)操作可以在常量时间内实现

缺点:求结点的孩子时需要遍历整个结构

用一组连续的存储空间来存储树的结点,同时在每个结点中附加一个指示器(整数域) ,用以指示双亲结点的位置(下标值) 。

图所示是一棵树及其双亲表示的存储结构。这种存储结构利用了任一结点的父结点唯一的性质。可以方便地直接找到任一结点的父结点,但求结点的子结点时需要扫描整个数组。

代码实现:

 1 // 1.双亲表示法
 2 // 优点:parent(tree, x)操作可以在常量时间内实现
 3 // 缺点:求结点的孩子时需要遍历整个结构
 4 function ParentTree() {
 5     this.nodes = [];
 6 }
 7 ParentTree.prototype = {
 8     constructor: ParentTree,
 9     getDepth: function () {
10         var maxDepth = 0;
11
12         for (var i = 0; i < this.nodes.length; i++) {
13             var dep = 0;
14             for (var j = i; j >= 0; j = this.nodes[i].parent) dep++;
15             if (dep > maxDepth) maxDepth = dep;
16         }
17
18         return maxDepth;
19     }
20 };
21 function ParentTreeNode(data, parent) {
22     // type: ParentTree
23     this.data = data || null;
24     // 双亲位置域 {Number}
25     this.parent = parent || 0;
26 }
27 var pt = new ParentTree();
28 pt.nodes.push(new ParentTreeNode(‘R‘, -1));
29 pt.nodes.push(new ParentTreeNode(‘A‘, 0));
30 pt.nodes.push(new ParentTreeNode(‘B‘, 0));
31 pt.nodes.push(new ParentTreeNode(‘C‘, 0));
32 pt.nodes.push(new ParentTreeNode(‘D‘, 1));
33 pt.nodes.push(new ParentTreeNode(‘E‘, 1));
34 pt.nodes.push(new ParentTreeNode(‘F‘, 3));
35 pt.nodes.push(new ParentTreeNode(‘G‘, 6));
36 pt.nodes.push(new ParentTreeNode(‘H‘, 6));
37 pt.nodes.push(new ParentTreeNode(‘I‘, 6));

2  孩子链表表示法

树中每个结点有多个指针域,每个指针指向其一棵子树的根结点。有两种结点结构。

⑴  定长结点结构

指针域的数目就是树的度。

其特点是:链表结构简单,但指针域的浪费明显。结点结构如下图(a)所示。在一棵有n个结点,度为k的树中必有n(k-1)+1空指针域。

⑵  不定长结点结构

树中每个结点的指针域数量不同,是该结点的度,如图(b) 所示。没有多余的指针域,但操作不便。

⑶  复合链表结构

对于树中的每个结点,其孩子结点用带头结点的单链表表示,表结点和头结点的结构如下图所示。

n个结点的树有n个(孩子)单链表(叶子结点的孩子链表为空),而n个头结点又组成一个线性表且以顺序存储结构表示。

复合链表结构代码:

 1 // 孩子表示法
 2
 3 function ChildTree() {
 4     this.nodes = [];
 5 }
 6 ChildTree.prototype = {
 7     constructor: ChildTree,
 8     getDepth: function () {
 9         var self = this;
10         return function subDepth(rootIndex) {
11             if (!self.nodes[rootIndex]) return 1;
12
13             for (var sd = 1, p = self.nodes[rootIndex]; p; p = p.next) {
14                 var d = subDepth(p.child);
15                 if (d > sd) sd = d;
16             }
17
18             return sd + 1;
19         }(this.data[0]);
20     }
21 };
22 /**
23  *
24  * @param {*} data
25  * @param {ChildTreeNode} firstChild 孩子链表头指针
26  * @constructor
27  */
28 function ChildTreeBox(data, firstChild) {
29     this.data = data;
30     this.firstChild = firstChild;
31 }
32 /**
33  * 孩子结点
34  *
35  * @param {Number} child
36  * @param {ChildTreeNode} next
37  * @constructor
38  */
39 function ChildTreeNode(child, next) {
40     this.child = child;
41     this.next = next;
42 }

孩子表示法便于涉及孩子的操作的实现,但不适用于parent操作。
我们可以把双亲表示法和孩子表示法结合起来。

3  孩子兄弟表示法(二叉树表示法)

以二叉链表作为树的存储结构。

两个指针域:分别指向结点的第一个子结点和下一个兄弟结点。结点类型定义如下:

 1 // 孩子兄弟表示法(二叉树表示法)
 2 // 可增设一个parent域实现parent操作
 3 function ChildSiblingTree(data) {
 4     this.data = data || null;
 5     this.firstChild = null;
 6     this.nextSibling = null;
 7 }
 8 ChildSiblingTree.prototype = {
 9     // 输出孩子兄弟链表表示的树的各边
10     print: function print() {
11         for (var child = this.firstChild; child; child = child.nextSibling) {
12             console.log(‘%c %c‘, this.data, child.data);
13             print.call(child);
14         }
15     },
16     // 求孩子兄弟链表表示的树的叶子数目
17     leafCount: function leafCount() {
18         if (!this.firstChild) return 1;
19         else {
20             var count = 0;
21             for (var child = this.firstChild; child; child = child.nextSibling) {
22                 count += leafCount.call(child);
23             }
24             return count;
25         }
26     },
27     // 求树的度
28     getDegree: function getDegree() {
29         if (!this.firstChild) return 0;
30         else {
31             var degree = 0;
32             for (var p = this.firstChild; p; p = p.nextSibling) degree++;
33
34             for (p = this.firstChild; p; p = p.nextSibling) {
35                 var d = getDegree.call(p);
36                 if (d > degree) degree = d;
37             }
38
39             return degree;
40         }
41     },
42     getDepth: function getDepth() {
43         if (this === global) return 0;
44         else {
45             for (var maxd = 0, p = this.firstChild; p; p = p.nextSibling) {
46                 var d = getDepth.call(p);
47                 if (d > maxd) maxd = d;
48             }
49
50             return maxd + 1;
51         }
52     }
53 };

森林与二叉树的转换

由于二叉树和树都可用二叉链表作为存储结构,对比各自的结点结构可以看出,以二叉链表作为媒介可以导出树和二叉树之间的一个对应关系。

◆ 从物理结构来看,树和二叉树的二叉链表是相同的,只是对指针的逻辑解释不同而已。

◆ 从树的二叉链表表示的定义可知,任何一棵和树对应的二叉树,其右子树一定为空。

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实现数据结构: 树和森林,布布扣,bubuko.com

时间: 2024-08-02 17:22:27

javascript实现数据结构: 树和森林的相关文章

数据结构 - 树和森林表示与遍历

双亲表示法(顺序存储结构) 用一组连续的存储空间来存储树的结点,同时在每个结点中附加一个指示器(整数域) ,用以指示双亲结点的位置(下标值) .数组元素及数组的类型定义如下: #define MAX_SIZE 100 typedef struct PTNode { ElemType data ; int parent ; }PTNode ; typedef struct { PTNode Nodes[MAX_SIZE] ; int root; /* 根结点位置 */ int num ; /* 结

javascript实现数据结构: 树和二叉树,二叉树的遍历和基本操作

树型结构是一类非常重要的非线性结构.直观地,树型结构是以分支关系定义的层次结构. 树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构:在数据库系统中,可用树来组织信息:在分析算法的行为时,可用树来描述其执行过程等等. 下面讲解的内容完整代码在这:https://github.com/LukeLin/data-structure-with-js/blob/master/Binary%20tree/BinaryTree.js 首先看看树的一些概念: 1.树(Tree)是n

javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径. ② 路径长度:结点路径上的分支数目称为路径长度. ③ 树的路径长度:从树根到每一个结点的路径长度之和. 以下图为例: A到F :结点路径 AEF : 路径长度(即边的数目) 2 : 树的路径长度:3*1+5*2+2*3=19: ④ 结点的带权路径长度:从该结点的到树的根结

数据结构---树、二叉树、森林

1.基本术语: 度:有两种度"结点的度"与"树的度".结点的度指的是一个结点子树的个数:树的度是指树中结点度的最大值. 叶子结点:指的是没有子树的结点. 层:树是有层次的,一般根结点为第0层.规定根结点到某结点的路径长度为该结点的层数. 深度:树中结点的最大层数 兄弟:同一双亲的结点,互为兄弟 堂兄弟:双亲在同一层次的结点,互为堂兄弟 祖先:从根结点到该结点的路径上的所有结点都是该结点的祖先. 子孙:以某一结点为根的子树上的所有结点都是该结点的子孙 森林:n棵互不相

数据结构——第三章树和二叉树:03树和森林

1.树的三种存储结构: (1)双亲表示法: #define MAX_TREE_SIZE 100 结点结构: typedef struct PTNode { Elem data; int parent; //双亲位置域 } PTNode; (2)孩子双亲链表表示法: typedef struct PTNode { Elem data; int parent; //双亲位置域 struct CTNode* nextchild; } *ChildPtr; (3)树的二叉链表(孩子-兄弟)存储表示法:

浅谈数据结构-树

树是一种数据结构,其中一个元素可以有两个或者多个数据元素,具有一对多的特点,用树结构来存储文件. 树的概念 结点的度:子结点的个数.例如结点1中有3个子结点,结点1的度是3. 树的度:树的度等于所有结点度中度最高的值.结点最高的度为3,树的度为3. 叶子结点:度为0的结点,即没有子结点的结点.例如:上图中3,5,6,7,9,10. 分支结点:除了叶子结点以外的结点,即度不为0的结点.例如:上面树的分支结点为1,2,4,8. 内部结点:除了根结点以及叶子结点或在分支结点的基础之上在去掉根结点.例如

javascript实现数据结构:线索二叉树

遍历二叉树是按一定的规则将树中的结点排列成一个线性序列,即是对非线性结构的线性化操作.如何找到遍历过程中动态得到的每个结点的直接前驱和直接后继(第一个和最后一个除外)?如何保存这些信息? 设一棵二叉树有n个结点,则有n-1条边(指针连线) , 而n个结点共有2n个指针域(Lchild和Rchild) ,显然有n+1个空闲指针域未用.则可以利用这些空闲的指针域来存放结点的直接前驱和直接后继信息. 对结点的指针域做如下规定: 1.若结点有左子树,则其leftChild域指示其左孩子,否则令leftC

javascript实现数据结构:广义表

原文:javascript实现数据结构:广义表  广义表是线性表的推广.广泛用于人工智能的表处理语言Lisp,把广义表作为基本的数据结构. 广义表一般记作: LS = (a1, a2, ..., an) LS是广义表的名称,n是它的长度,ai可以是单个元素,也可以是广义表,分别称为广义表LS的原子和子表.习惯上,用大写字母表示广义表的名称,小写字母表示原子.当广义表LS非空时,称第一个元素a1为LS的表头,称其余元素组成的表(a2, a3, ..., an)是LS的表尾. 下面列举一些广义表的例

数据结构--树(定义与存储结构)

树基本定义 树的定义 数是具有n个节点的有限集.如图即是一个树形结构. 节点分类 节点的度:一个节点拥有的子节点即成为节点的度,比如A节点,有B和C两个子节点,那么A节点的度=2. 叶节点(终端节点):没有子节点的节点,比如G.H.I.... 如图: 节点间关系 孩子节点:某一个节点的子节点称为孩子节点.比如B.C节点是A节点的孩子节点. 双亲节点:与孩子节点相反.比如,A节点是B.C的双亲节点. 兄弟节点:同一个双亲节点的孩子节点,之间称为兄弟节点.比如,B.C为兄弟节点. 如图: 树的存储结