首页 > 代码库 > javascript实现数据结构:广义表

javascript实现数据结构:广义表

原文:javascript实现数据结构:广义表

 广义表是线性表的推广。广泛用于人工智能的表处理语言Lisp,把广义表作为基本的数据结构。

广义表一般记作:

     LS = (a1, a2, ..., an)

LS是广义表的名称,n是它的长度,ai可以是单个元素,也可以是广义表,分别称为广义表LS的原子子表。习惯上,用大写字母表示广义表的名称,小写字母表示原子。当广义表LS非空时,称第一个元素a1为LS的表头,称其余元素组成的表(a2, a3, ..., an)是LS的表尾

 

下面列举一些广义表的例子:

1.A = () ---- A是一个空表,它的长度为0。

2.B = (e) ---- 列表B只有一个原子e,B的长度为1。

3.C = (a, (b, c, d)) ---- 列表C的长度为2,两个元素分别为原子a和子表(b, c, d)。

4.D = (A, B, C) ---- 列表D的长度为3,3个元素都是列表。显示,将子表的值代入后,则有D = ((), (e), (a, (b, c, d)))。

5.E = (a, E) ---- 这是一个递归的表,它的长度为2.E相当于一个无限的列表E = (a, (a, (a, ...)))。

 

下图为列表的图形表示:

从定义和例子可推出三个结论:

1)列表的元素可以是子表,而子表的元素还可以是子表。由此,列表是一个多层次的结构,可以用图形象地表示。

2)列表可为其它列表所共享。列表A,B和C为D的子表,则在D中可以不必列出子表的值。

3)列表可以是一个递归的表,即列表也可以是其本身的一个子表。例如列表E。

 

然后我们根据结论可知:

任何一个非空列表其表头可能是原子,也可能是列表,而其表尾必定为列表。

 

由于广义表中的数据元素可以具有不同的结构(或是原子,或是列表),因此难以用顺序存储结构表示,通常采用链式存储结构,每个数据元素可用一个结点表示。

下面是两种稍微不同的存储结构表示:

 1 var ATOM = 0;
 2 var LIST = 1;
 3 
 4 // 广义表的头尾链表存储表示
 5 function GLNode() {
 6     // 公共部分,用于区分原子结点和表结点
 7     this.tag = undefined;
 8 
 9     // atom是原子结点的值域
10     this.atom = null;
11     // ptr是表结点的指针域
12     this.ptr = {
13         // ptr.hp和ptr.tp分别指向表头和表尾
14         hp: null,
15         tp: null
16     };
17 }
18 
19 // 广义表的扩展线性链表存储表示
20 function GLNode2() {
21     // 公共部分,用于区分原子结点和表结点
22     this.tag = undefined;
23 
24     // 原子结点的值域
25     this.atom = null;
26     // 表结点的表头指针
27     this.hp = null;
28 
29     // 相当于线性链表的next,指向下一个元素结点
30     this.tp = null;
31 }

 

下列分别为两个存储结构的示例图:

1.GLNode

 

2.GLNode2

 

两种存储结构没有大的区别,可根据自己的习惯选择。

 

广义表的递归算法

我们知道递归定义的归纳项是用来描述如何实现从当前状态到终结状态的转化。

由于递归函数的设计用的是归纳思维的方法,则在设计递归函数时,应注意:
(1)首先应书写函数的首部和规格说明,严格定义函数的功能和接口(递归调用的界面),对求精函数中所得的和原问题性质相同的字问题,只要接口一致,便可进行递归调用。
(2)对函数中的每一个递归调用都看成只是一个简单的操作,只要接口一致,必能实现规格说明中定义的功能,切忌想得太深太远。

 

求广义表的深度

广义表的深度定义为广义表中括弧的重数,是广义表的一种量度。
设非空广义表为:
    LS = (a1, a2, ..., an)

其中ai(i = 1, 2, ..., n)或为原子或为LS的子表,则求LS的深度可分解为n个子问题,每个子问题为求ai的深度,若ai是原子,则由定义其深度为零,若ai是广义表,则递归处理,而LS的深度为各ai(i = 1, 2, ..., n)的深度最大值加1.空表也是广义表,且深度为1.

广义表的深度DEPTH(LS)的递归定义为:
    基本项: DEPTH(LS) = 1 当LS为空表时
                 DEPTH(LS) = 0 当LS为原子时
    归纳项: DEPTH(LS) = 1 + MAX{DEPTH(ai)} 1 <= i <= n

下面为采用头尾链表存储结构,求广义表的深度的代码:

 1 // 采用头尾链表存储结构,求广义表的深度
 2 GLNode.prototype.depth = function () {
 3     return getDepth(this);
 4 };
 5 
 6 function getDepth(gList) {
 7     if (!gList) return 1;
 8     else if (gList.tag === ATOM) return 0;
 9 
10     var m = getDepth(gList.ptr.hp) + 1;
11     var n = getDepth(gList.ptr.tp);
12 
13     return m > n ? m : n;
14 }

然后下面是广义表基本操作的代码(都是涉及到递归算法设计):

  1 // 复制广义表
  2 GLNode.prototype.copyList = function (gList) {
  3     gList.tag = this.tag;
  4 
  5     if (this.tag === ATOM) {
  6         gList.atom = this.atom;
  7     } else {
  8         if (this.ptr.hp) {
  9             gList.ptr.hp = new GLNode();
 10             this.copyList.call(this.ptr.hp, gList.ptr.hp);
 11         }
 12         if (this.ptr.tp) {
 13             gList.ptr.tp = new GLNode();
 14             this.copyList.call(this.ptr.tp, gList.ptr.tp);
 15         }
 16     }
 17 };
 18 
 19 function isWord(str){
 20     return /^[\w-]+$/.test(str);
 21 }
 22 
 23 // 采用头尾链表存储结构,由广义表的书写形式串创建广义表
 24 GLNode.prototype.createGList = function (string) {
 25     string = string.trim();
 26 
 27     // 创建单原子广义表
 28     var q;
 29     if (isWord(string)) {
 30         this.tag = ATOM;
 31         this.atom = string;
 32     } else {
 33         this.tag = LIST;
 34         var p = this;
 35 
 36         // 脱外层括号
 37         var sub = string.substr(1, string.length - 2);
 38 
 39         do {
 40             var hsub;
 41             var n = sub.length;
 42             var i = 0;
 43             var k = 0;
 44             var ch;
 45 
 46             do {
 47                 ch = sub[i++];
 48                 if (ch == ‘(‘) ++k;
 49                 else if (ch == ‘)‘) --k;
 50             } while (i < n && (ch != ‘,‘ || k != 0));
 51 
 52             // i为第一个逗号分隔索引
 53             if (i < n) {
 54                 hsub = sub.substr(0, i - 1);
 55                 sub = sub.substr(i, n - i);
 56 
 57                 // 最后一组
 58             } else {
 59                 hsub = sub;
 60                 sub = ‘‘;
 61             }
 62 
 63             if(hsub === ‘()‘)
 64                 p.ptr.hp = null;
 65             else
 66                 // 创建表头结点
 67                 this.createGList.call((p.ptr.hp = new GLNode()), hsub);
 68 
 69             q = p;
 70 
 71             // 创建表尾结点
 72             if (sub) {
 73                 p = new GLNode();
 74                 p.tag = LIST;
 75                 q.ptr.tp = p;
 76             }
 77         } while (sub);
 78 
 79         q.ptr.tp = null;
 80     }
 81 };
 82 
 83 var node = new GLNode();
 84 node.createGList(‘((), (ea), (sa, (bd, ce, dh)))‘);
 85 console.log(node.depth());
 86 
 87 GLNode.equal = function equal(gList1, gList2) {
 88     // 空表时相等的
 89     if (!gList1 && !gList2) return true;
 90     if (gList1.tag === ATOM && gList2.tag === ATOM && gList1.atom === gList2.atom) return true;
 91 
 92     if (gList1.tag === LIST && gList2.tag === LIST) {
 93         // 表头表尾都相等
 94         if (equal(gList1.ptr.hp, gList2.ptr.hp) && equal(gList1.ptr.tp, gList2.ptr.tp)) return true;
 95     }
 96 
 97     return false;
 98 };
 99 
100 // 递归逆转广义表
101 GLNode.prototype.reverse = function reverse() {
102     var ptr = [];
103     // 当A不为原子且表尾非空时才需逆转
104     if (this.tag === LIST && this.ptr.tp) {
105         for (var i = 0, p = this; p; p = p.ptr.tp, i++) {
106             // 逆转各子表
107             if (p.ptr.hp) reverse.call(p.ptr.hp);
108 
109             ptr[i] = p.ptr.hp;
110         }
111 
112         // 重新按逆序排列各子表的顺序
113         for (p = this; p; p = p.ptr.tp)
114             p.ptr.hp = ptr[--i];
115     }
116 };
117 
118 var global = Function(‘return this‘)();
119 GLNode.prototype.toString = function () {
120     var str = ‘‘;
121     if (this == global) str = ‘()‘;
122     else if (this.tag === ATOM) str = this.atom;  // 原子
123     else {
124         str += ‘(‘;
125 
126         for (var p = this; p; p = p.ptr.tp) {
127             str += this.toString.call(p.ptr.hp);
128             if (p.ptr.tp) str += ‘, ‘;
129         }
130         str += ‘)‘;
131     }
132 
133     return str;
134 };
135 
136 // 按层序输出广义表
137 // 层序遍历的问题,一般都是借助队列来完成的,每次从队头
138 // 取出一个元素的同时把它下一层的孩子插入队尾,这是层序遍历的基本思想
139 GLNode.prototype.orderPrint = function(){
140     var queue = [];
141     for(var p = this; p; p = p.ptr.tp) queue.push(p);
142 
143     while(queue.length){
144         var r = queue.shift();
145         if(r.tag === ATOM) console.log(r.atom);
146         else {
147             for(r = r.ptr.hp; r; r = r.ptr.tp)
148                 queue.push(r);
149         }
150     }
151 };
152 
153 // 使用链队列
154 var Queue = require(‘../Queue/Queue.js‘).Queue;
155 GLNode.prototype.orderPrint2 = function(){
156     var queue = new Queue();
157 
158     for(var p = this; p; p = p.ptr.tp) queue.enQueue(p);
159 
160     while(queue.size){
161         var r = queue.deQueue();
162         if(r.tag === ATOM) console.log(r.atom);
163         else {
164             for(r = r.ptr.hp; r; r = r.ptr.tp)
165                 queue.enQueue(r);
166         }
167     }
168 };
169 
170 console.log(node + ‘‘);
171 node.reverse();
172 console.log(node + ‘‘);
173 
174 var node2 = new GLNode();
175 node.copyList(node2);
176 console.log(GLNode.equal(node, node2));
177 
178 console.log(node + ‘‘);
179 console.time(‘A‘);
180 node.orderPrint();
181 console.timeEnd(‘A‘);
182 
183 console.log(‘------------------------------------‘);
184 console.time(‘B‘);
185 node.orderPrint2();
186 console.timeEnd(‘B‘);

 

广义表的运用:

m元多项式表示

如果用线性表来表示,则每个数据元素需要m+1个数据项,以存储一个系数和m个指数值,这将产生两个问题。
一是无论多项式中各项的变元数是多是少,若都按m个变元分配存储空间,则将造成浪费;反之,若按各项实际的变元数分配存储空间,就会造成结点的大小不匀,给操作带来不便。二是对m值不同的多项式,线性表中的结点大小也不同,这同样引起存储管理的不便。
故不适于用线性表表示。

例如三元多项式:
    P(x, y, z) = x(10)y(3)z(2) + 2x(6)y(3)z(2) + 3x(5)y(2)z(2) + x(4)y(4)z + 2yz + 15

如若改写为:
    P(x, y, z) = ((x(10) + 2x(6))y(3) + 3x(5)y(2))z(2) + ((x(4) + 6x(3))y(4) + 2y)z + 15

用广义表表示:
    P = z((A, 2), (B, 1), (15, 0))
    A = y((C, 3), (D, 2))
    B = y((E, 4), (F, 1))
    C = x((1, 10), (2, 6))
    D = x((3, 5))
    E = x((1, 4), (6, 3))
    F = x((2, 0))

下面为用广义表描述m元多项式的存储结构:

 1 function MPNode() {
 2     // 区分原子结点和表结点
 3     this.tag = undefined;
 4     // 指数域
 5     this.exp = 0;
 6 
 7     // 系数域
 8     this.coef = 0;
 9     // 表结点的表头指针
10     this.hp = null;
11 
12     // 相当于线性表的next,指向下一个元素结点
13     this.tp = null;
14 }

篇幅有限,就没有做其他操作,以后有空再补回吧。

 

所有代码:

  1 /**
  2  * 广义表
  3  *
  4  * 广义表是线性表的推广。广泛用于人工智能的表处理语言Lisp,把广义表作为基本的数据结构。
  5  * 广义表一般记作:
  6  *      LS = (a1, a2, ..., an)
  7  * LS是广义表的名称,n是它的长度,ai可以是单个元素,也可以是广义表,分别称为广义表LS的原子和子表。习惯上,用大写字母表示广义表的名称,小写字母表示原子。当广义表LS非空时,称第一个元素a1为LS的表头,称其余元素组成的表(a2, a3, ..., an)是LS的表尾。
  8  *
  9  * 下面列举一些广义表的例子:
 10  * 1.A = () ---- A是一个空表,它的长度为0。
 11  * 2.B = (e) ---- 列表B只有一个原子e,B的长度为1。
 12  * 3.C = (a, (b, c, d)) ---- 列表C的长度为2,两个元素分别为原子a和子表(b, c, d)。
 13  * 4.D = (A, B, C) ---- 列表D的长度为3,3个元素都是列表。显示,将子表的值代入后,则有D = ((), (e), (a, (b, c, d)))。
 14  * 5.E = (a, E) ---- 这是一个递归的表,它的长度为2.E相当于一个无限的列表E = (a, (a, (a, ...)))。
 15  *
 16  * 1)列表的元素可以是子表,而子表的元素还可以是子表。由此,列表是一个多层次的结构,可以用图形象地表示。
 17  * 2)列表可为其它列表所共享。列表A,B和C为D的子表,则在D中可以不必列出子表的值。
 18  * 3)列表可以是一个递归的表,即列表也可以是其本身的一个子表。例如列表E。
 19  *
 20  * 任何一个非空列表其表头可能是原子,也可能是列表,而其表尾必定为列表。
 21  *
 22  */
 23 
 24 var ATOM = 0;
 25 var LIST = 1;
 26 
 27 // 广义表的头尾链表存储表示
 28 function GLNode() {
 29     // 公共部分,用于区分原子结点和表结点
 30     this.tag = undefined;
 31 
 32     // atom是原子结点的值域
 33     this.atom = null;
 34     // ptr是表结点的指针域
 35     this.ptr = {
 36         // ptr.hp和ptr.tp分别指向表头和表尾
 37         hp: null,
 38         tp: null
 39     };
 40 }
 41 
 42 // 广义表的扩展线性链表存储表示
 43 function GLNode2() {
 44     // 公共部分,用于区分原子结点和表结点
 45     this.tag = undefined;
 46 
 47     // 原子结点的值域
 48     this.atom = null;
 49     // 表结点的表头指针
 50     this.hp = null;
 51 
 52     // 相当于线性链表的next,指向下一个元素结点
 53     this.tp = null;
 54 }
 55 
 56 
 57 /*
 58 广义表的递归算法
 59 
 60 递归定义的归纳项描述了如何实现从当前状态到终结状态的转化。
 61 
 62 由于递归函数的设计用的是归纳思维的方法,则在设计递归函数时,应注意:
 63 (1)首先应书写函数的首部和规格说明,严格定义函数的功能和接口(递归调用的界面),对求精函数中所得的和原问题性质相同的字问题,只要接口一致,便可进行递归调用。
 64 (2)对函数中的每一个递归调用都看成只是一个简单的操作,只要接口一致,必能实现规格说明中定义的功能,切忌想得太深太远。
 65  */
 66 
 67 /*
 68 求广义表的深度
 69 
 70 广义表的深度定义为广义表中括弧的重数,是广义表的一种量度。
 71 设非空广义表为:
 72         LS = (a1, a2, ..., an)
 73 
 74 其中ai(i = 1, 2, ..., n)或为原子或为LS的子表,则求LS的深度可分解为n个子问题,每个子问题为求ai的深度,若ai是原子,则由定义其深度为零,若ai是广义表,则递归处理,而LS的深度为各ai(i = 1, 2, ..., n)的深度最大值加1.空表也是广义表,且深度为1.
 75 
 76 广义表的深度DEPTH(LS)的递归定义为:
 77     基本项:    DEPTH(LS) = 1   当LS为空表时
 78                 DEPTH(LS) = 0   当LS为原子时
 79     归纳项:    DEPTH(LS) = 1 + MAX{DEPTH(ai)}  1 <= i <= n
 80  */
 81 
 82 // 采用头尾链表存储结构,求广义表的深度
 83 GLNode.prototype.depth = function () {
 84     return getDepth(this);
 85 };
 86 
 87 function getDepth(gList) {
 88     if (!gList) return 1;
 89     else if (gList.tag === ATOM) return 0;
 90 
 91     var m = getDepth(gList.ptr.hp) + 1;
 92     var n = getDepth(gList.ptr.tp);
 93 
 94     return m > n ? m : n;
 95 }
 96 
 97 // 复制广义表
 98 GLNode.prototype.copyList = function (gList) {
 99     gList.tag = this.tag;
100 
101     if (this.tag === ATOM) {
102         gList.atom = this.atom;
103     } else {
104         if (this.ptr.hp) {
105             gList.ptr.hp = new GLNode();
106             this.copyList.call(this.ptr.hp, gList.ptr.hp);
107         }
108         if (this.ptr.tp) {
109             gList.ptr.tp = new GLNode();
110             this.copyList.call(this.ptr.tp, gList.ptr.tp);
111         }
112     }
113 };
114 
115 function isWord(str){
116     return /^[\w-]+$/.test(str);
117 }
118 
119 // 采用头尾链表存储结构,由广义表的书写形式串创建广义表
120 GLNode.prototype.createGList = function (string) {
121     string = string.trim();
122 
123     // 创建单原子广义表
124     var q;
125     if (isWord(string)) {
126         this.tag = ATOM;
127         this.atom = string;
128     } else {
129         this.tag = LIST;
130         var p = this;
131 
132         // 脱外层括号
133         var sub = string.substr(1, string.length - 2);
134 
135         do {
136             var hsub;
137             var n = sub.length;
138             var i = 0;
139             var k = 0;
140             var ch;
141 
142             do {
143                 ch = sub[i++];
144                 if (ch == ‘(‘) ++k;
145                 else if (ch == ‘)‘) --k;
146             } while (i < n && (ch != ‘,‘ || k != 0));
147 
148             // i为第一个逗号分隔索引
149             if (i < n) {
150                 hsub = sub.substr(0, i - 1);
151                 sub = sub.substr(i, n - i);
152 
153                 // 最后一组
154             } else {
155                 hsub = sub;
156                 sub = ‘‘;
157             }
158 
159             if(hsub === ‘()‘)
160                 p.ptr.hp = null;
161             else
162                 // 创建表头结点
163                 this.createGList.call((p.ptr.hp = new GLNode()), hsub);
164 
165             q = p;
166 
167             // 创建表尾结点
168             if (sub) {
169                 p = new GLNode();
170                 p.tag = LIST;
171                 q.ptr.tp = p;
172             }
173         } while (sub);
174 
175         q.ptr.tp = null;
176     }
177 };
178 
179 var node = new GLNode();
180 node.createGList(‘((), (ea), (sa, (bd, ce, dh)))‘);
181 console.log(node.depth());
182 
183 GLNode.equal = function equal(gList1, gList2) {
184     // 空表时相等的
185     if (!gList1 && !gList2) return true;
186     if (gList1.tag === ATOM && gList2.tag === ATOM && gList1.atom === gList2.atom) return true;
187 
188     if (gList1.tag === LIST && gList2.tag === LIST) {
189         // 表头表尾都相等
190         if (equal(gList1.ptr.hp, gList2.ptr.hp) && equal(gList1.ptr.tp, gList2.ptr.tp)) return true;
191     }
192 
193     return false;
194 };
195 
196 // 递归逆转广义表
197 GLNode.prototype.reverse = function reverse() {
198     var ptr = [];
199     // 当A不为原子且表尾非空时才需逆转
200     if (this.tag === LIST && this.ptr.tp) {
201         for (var i = 0, p = this; p; p = p.ptr.tp, i++) {
202             // 逆转各子表
203             if (p.ptr.hp) reverse.call(p.ptr.hp);
204 
205             ptr[i] = p.ptr.hp;
206         }
207 
208         // 重新按逆序排列各子表的顺序
209         for (p = this; p; p = p.ptr.tp)
210             p.ptr.hp = ptr[--i];
211     }
212 };
213 
214 var global = Function(‘return this‘)();
215 GLNode.prototype.toString = function () {
216     var str = ‘‘;
217     if (this == global) str = ‘()‘;
218     else if (this.tag === ATOM) str = this.atom;  // 原子
219     else {
220         str += ‘(‘;
221 
222         for (var p = this; p; p = p.ptr.tp) {
223             str += this.toString.call(p.ptr.hp);
224             if (p.ptr.tp) str += ‘, ‘;
225         }
226         str += ‘)‘;
227     }
228 
229     return str;
230 };
231 
232 // 按层序输出广义表
233 // 层序遍历的问题,一般都是借助队列来完成的,每次从队头
234 // 取出一个元素的同时把它下一层的孩子插入队尾,这是层序遍历的基本思想
235 GLNode.prototype.orderPrint = function(){
236     var queue = [];
237     for(var p = this; p; p = p.ptr.tp) queue.push(p);
238 
239     while(queue.length){
240         var r = queue.shift();
241         if(r.tag === ATOM) console.log(r.atom);
242         else {
243             for(r = r.ptr.hp; r; r = r.ptr.tp)
244                 queue.push(r);
245         }
246     }
247 };
248 
249 // 使用链队列
250 var Queue = require(‘../Queue/Queue.js‘).Queue;
251 GLNode.prototype.orderPrint2 = function(){
252     var queue = new Queue();
253 
254     for(var p = this; p; p = p.ptr.tp) queue.enQueue(p);
255 
256     while(queue.size){
257         var r = queue.deQueue();
258         if(r.tag === ATOM) console.log(r.atom);
259         else {
260             for(r = r.ptr.hp; r; r = r.ptr.tp)
261                 queue.enQueue(r);
262         }
263     }
264 };
265 
266 console.log(node + ‘‘);
267 node.reverse();
268 console.log(node + ‘‘);
269 
270 var node2 = new GLNode();
271 node.copyList(node2);
272 console.log(GLNode.equal(node, node2));
273 
274 console.log(node + ‘‘);
275 console.time(‘A‘);
276 node.orderPrint();
277 console.timeEnd(‘A‘);
278 
279 console.log(‘------------------------------------‘);
280 console.time(‘B‘);
281 node.orderPrint2();
282 console.timeEnd(‘B‘);
283 
284 /*
285 m元多项式表示
286 
287 如果用线性表来表示,则每个数据元素需要m+1个数据项,以存储一个系数和m个指数值,这将产生两个问题。
288 一是无论多项式中各项的变元数是多是少,若都按m个变元分配存储空间,则将造成浪费;反之,若按各项实际的变元数分配存储空间,就会造成结点的大小不匀,给操作带来不便。二是对m值不同的多项式,线性表中的结点大小也不同,这同样引起存储管理的不便。
289 故不适于用线性表表示。
290 
291 例如三元多项式:
292     P(x, y, z) = x(10)y(3)z(2) + 2x(6)y(3)z(2) + 3x(5)y(2)z(2) + x(4)y(4)z + 2yz + 15
293 
294 如若改写为:
295     P(x, y, z) = ((x(10) + 2x(6))y(3) + 3x(5)y(2))z(2) + ((x(4) + 6x(3))y(4) + 2y)z + 15
296 
297 用广义表表示:
298     P = z((A, 2), (B, 1), (15, 0))
299     A = y((C, 3), (D, 2))
300     B = y((E, 4), (F, 1))
301     C = x((1, 10), (2, 6))
302     D = x((3, 5))
303     E = x((1, 4), (6, 3))
304     F = x((2, 0))
305 
306 
307  */
308 
309 function MPNode() {
310     // 区分原子结点和表结点
311     this.tag = undefined;
312     // 指数域
313     this.exp = 0;
314 
315     // 系数域
316     this.coef = 0;
317     // 表结点的表头指针
318     this.hp = null;
319 
320     // 相当于线性表的next,指向下一个元素结点
321     this.tp = null;
322 }
View Code