原文: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。

然后我们根据结论可知:

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

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

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

 var ATOM = 0;
var LIST = 1; // 广义表的头尾链表存储表示
function GLNode() {
// 公共部分,用于区分原子结点和表结点
this.tag = undefined; // atom是原子结点的值域
this.atom = null;
// ptr是表结点的指针域
this.ptr = {
// ptr.hp和ptr.tp分别指向表头和表尾
hp: null,
tp: null
};
} // 广义表的扩展线性链表存储表示
function GLNode2() {
// 公共部分,用于区分原子结点和表结点
this.tag = undefined; // 原子结点的值域
this.atom = null;
// 表结点的表头指针
this.hp = null; // 相当于线性链表的next,指向下一个元素结点
this.tp = null;
}

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

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

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

 // 采用头尾链表存储结构,求广义表的深度
GLNode.prototype.depth = function () {
return getDepth(this);
}; function getDepth(gList) {
if (!gList) return 1;
else if (gList.tag === ATOM) return 0; var m = getDepth(gList.ptr.hp) + 1;
var n = getDepth(gList.ptr.tp); return m > n ? m : n;
}

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

 // 复制广义表
GLNode.prototype.copyList = function (gList) {
gList.tag = this.tag; if (this.tag === ATOM) {
gList.atom = this.atom;
} else {
if (this.ptr.hp) {
gList.ptr.hp = new GLNode();
this.copyList.call(this.ptr.hp, gList.ptr.hp);
}
if (this.ptr.tp) {
gList.ptr.tp = new GLNode();
this.copyList.call(this.ptr.tp, gList.ptr.tp);
}
}
}; function isWord(str){
return /^[\w-]+$/.test(str);
} // 采用头尾链表存储结构,由广义表的书写形式串创建广义表
GLNode.prototype.createGList = function (string) {
string = string.trim(); // 创建单原子广义表
var q;
if (isWord(string)) {
this.tag = ATOM;
this.atom = string;
} else {
this.tag = LIST;
var p = this; // 脱外层括号
var sub = string.substr(1, string.length - 2); do {
var hsub;
var n = sub.length;
var i = 0;
var k = 0;
var ch; do {
ch = sub[i++];
if (ch == '(') ++k;
else if (ch == ')') --k;
} while (i < n && (ch != ',' || k != 0)); // i为第一个逗号分隔索引
if (i < n) {
hsub = sub.substr(0, i - 1);
sub = sub.substr(i, n - i); // 最后一组
} else {
hsub = sub;
sub = '';
} if(hsub === '()')
p.ptr.hp = null;
else
// 创建表头结点
this.createGList.call((p.ptr.hp = new GLNode()), hsub); q = p; // 创建表尾结点
if (sub) {
p = new GLNode();
p.tag = LIST;
q.ptr.tp = p;
}
} while (sub); q.ptr.tp = null;
}
}; var node = new GLNode();
node.createGList('((), (ea), (sa, (bd, ce, dh)))');
console.log(node.depth()); GLNode.equal = function equal(gList1, gList2) {
// 空表时相等的
if (!gList1 && !gList2) return true;
if (gList1.tag === ATOM && gList2.tag === ATOM && gList1.atom === gList2.atom) return true; if (gList1.tag === LIST && gList2.tag === LIST) {
// 表头表尾都相等
if (equal(gList1.ptr.hp, gList2.ptr.hp) && equal(gList1.ptr.tp, gList2.ptr.tp)) return true;
} return false;
}; // 递归逆转广义表
GLNode.prototype.reverse = function reverse() {
var ptr = [];
// 当A不为原子且表尾非空时才需逆转
if (this.tag === LIST && this.ptr.tp) {
for (var i = 0, p = this; p; p = p.ptr.tp, i++) {
// 逆转各子表
if (p.ptr.hp) reverse.call(p.ptr.hp); ptr[i] = p.ptr.hp;
} // 重新按逆序排列各子表的顺序
for (p = this; p; p = p.ptr.tp)
p.ptr.hp = ptr[--i];
}
}; var global = Function('return this')();
GLNode.prototype.toString = function () {
var str = '';
if (this == global) str = '()';
else if (this.tag === ATOM) str = this.atom; // 原子
else {
str += '('; for (var p = this; p; p = p.ptr.tp) {
str += this.toString.call(p.ptr.hp);
if (p.ptr.tp) str += ', ';
}
str += ')';
} return str;
}; // 按层序输出广义表
// 层序遍历的问题,一般都是借助队列来完成的,每次从队头
// 取出一个元素的同时把它下一层的孩子插入队尾,这是层序遍历的基本思想
GLNode.prototype.orderPrint = function(){
var queue = [];
for(var p = this; p; p = p.ptr.tp) queue.push(p); while(queue.length){
var r = queue.shift();
if(r.tag === ATOM) console.log(r.atom);
else {
for(r = r.ptr.hp; r; r = r.ptr.tp)
queue.push(r);
}
}
}; // 使用链队列
var Queue = require('../Queue/Queue.js').Queue;
GLNode.prototype.orderPrint2 = function(){
var queue = new Queue(); for(var p = this; p; p = p.ptr.tp) queue.enQueue(p); while(queue.size){
var r = queue.deQueue();
if(r.tag === ATOM) console.log(r.atom);
else {
for(r = r.ptr.hp; r; r = r.ptr.tp)
queue.enQueue(r);
}
}
}; console.log(node + '');
node.reverse();
console.log(node + ''); var node2 = new GLNode();
node.copyList(node2);
console.log(GLNode.equal(node, node2)); console.log(node + '');
console.time('A');
node.orderPrint();
console.timeEnd('A'); console.log('------------------------------------');
console.time('B');
node.orderPrint2();
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元多项式的存储结构:

 function MPNode() {
// 区分原子结点和表结点
this.tag = undefined;
// 指数域
this.exp = 0; // 系数域
this.coef = 0;
// 表结点的表头指针
this.hp = null; // 相当于线性表的next,指向下一个元素结点
this.tp = null;
}

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

所有代码:

 /**
* 广义表
*
* 广义表是线性表的推广。广泛用于人工智能的表处理语言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。
*
* 任何一个非空列表其表头可能是原子,也可能是列表,而其表尾必定为列表。
*
*/ var ATOM = 0;
var LIST = 1; // 广义表的头尾链表存储表示
function GLNode() {
// 公共部分,用于区分原子结点和表结点
this.tag = undefined; // atom是原子结点的值域
this.atom = null;
// ptr是表结点的指针域
this.ptr = {
// ptr.hp和ptr.tp分别指向表头和表尾
hp: null,
tp: null
};
} // 广义表的扩展线性链表存储表示
function GLNode2() {
// 公共部分,用于区分原子结点和表结点
this.tag = undefined; // 原子结点的值域
this.atom = null;
// 表结点的表头指针
this.hp = null; // 相当于线性链表的next,指向下一个元素结点
this.tp = null;
} /*
广义表的递归算法 递归定义的归纳项描述了如何实现从当前状态到终结状态的转化。 由于递归函数的设计用的是归纳思维的方法,则在设计递归函数时,应注意:
(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
*/ // 采用头尾链表存储结构,求广义表的深度
GLNode.prototype.depth = function () {
return getDepth(this);
}; function getDepth(gList) {
if (!gList) return 1;
else if (gList.tag === ATOM) return 0; var m = getDepth(gList.ptr.hp) + 1;
var n = getDepth(gList.ptr.tp); return m > n ? m : n;
} // 复制广义表
GLNode.prototype.copyList = function (gList) {
gList.tag = this.tag; if (this.tag === ATOM) {
gList.atom = this.atom;
} else {
if (this.ptr.hp) {
gList.ptr.hp = new GLNode();
this.copyList.call(this.ptr.hp, gList.ptr.hp);
}
if (this.ptr.tp) {
gList.ptr.tp = new GLNode();
this.copyList.call(this.ptr.tp, gList.ptr.tp);
}
}
}; function isWord(str){
return /^[\w-]+$/.test(str);
} // 采用头尾链表存储结构,由广义表的书写形式串创建广义表
GLNode.prototype.createGList = function (string) {
string = string.trim(); // 创建单原子广义表
var q;
if (isWord(string)) {
this.tag = ATOM;
this.atom = string;
} else {
this.tag = LIST;
var p = this; // 脱外层括号
var sub = string.substr(1, string.length - 2); do {
var hsub;
var n = sub.length;
var i = 0;
var k = 0;
var ch; do {
ch = sub[i++];
if (ch == '(') ++k;
else if (ch == ')') --k;
} while (i < n && (ch != ',' || k != 0)); // i为第一个逗号分隔索引
if (i < n) {
hsub = sub.substr(0, i - 1);
sub = sub.substr(i, n - i); // 最后一组
} else {
hsub = sub;
sub = '';
} if(hsub === '()')
p.ptr.hp = null;
else
// 创建表头结点
this.createGList.call((p.ptr.hp = new GLNode()), hsub); q = p; // 创建表尾结点
if (sub) {
p = new GLNode();
p.tag = LIST;
q.ptr.tp = p;
}
} while (sub); q.ptr.tp = null;
}
}; var node = new GLNode();
node.createGList('((), (ea), (sa, (bd, ce, dh)))');
console.log(node.depth()); GLNode.equal = function equal(gList1, gList2) {
// 空表时相等的
if (!gList1 && !gList2) return true;
if (gList1.tag === ATOM && gList2.tag === ATOM && gList1.atom === gList2.atom) return true; if (gList1.tag === LIST && gList2.tag === LIST) {
// 表头表尾都相等
if (equal(gList1.ptr.hp, gList2.ptr.hp) && equal(gList1.ptr.tp, gList2.ptr.tp)) return true;
} return false;
}; // 递归逆转广义表
GLNode.prototype.reverse = function reverse() {
var ptr = [];
// 当A不为原子且表尾非空时才需逆转
if (this.tag === LIST && this.ptr.tp) {
for (var i = 0, p = this; p; p = p.ptr.tp, i++) {
// 逆转各子表
if (p.ptr.hp) reverse.call(p.ptr.hp); ptr[i] = p.ptr.hp;
} // 重新按逆序排列各子表的顺序
for (p = this; p; p = p.ptr.tp)
p.ptr.hp = ptr[--i];
}
}; var global = Function('return this')();
GLNode.prototype.toString = function () {
var str = '';
if (this == global) str = '()';
else if (this.tag === ATOM) str = this.atom; // 原子
else {
str += '('; for (var p = this; p; p = p.ptr.tp) {
str += this.toString.call(p.ptr.hp);
if (p.ptr.tp) str += ', ';
}
str += ')';
} return str;
}; // 按层序输出广义表
// 层序遍历的问题,一般都是借助队列来完成的,每次从队头
// 取出一个元素的同时把它下一层的孩子插入队尾,这是层序遍历的基本思想
GLNode.prototype.orderPrint = function(){
var queue = [];
for(var p = this; p; p = p.ptr.tp) queue.push(p); while(queue.length){
var r = queue.shift();
if(r.tag === ATOM) console.log(r.atom);
else {
for(r = r.ptr.hp; r; r = r.ptr.tp)
queue.push(r);
}
}
}; // 使用链队列
var Queue = require('../Queue/Queue.js').Queue;
GLNode.prototype.orderPrint2 = function(){
var queue = new Queue(); for(var p = this; p; p = p.ptr.tp) queue.enQueue(p); while(queue.size){
var r = queue.deQueue();
if(r.tag === ATOM) console.log(r.atom);
else {
for(r = r.ptr.hp; r; r = r.ptr.tp)
queue.enQueue(r);
}
}
}; console.log(node + '');
node.reverse();
console.log(node + ''); var node2 = new GLNode();
node.copyList(node2);
console.log(GLNode.equal(node, node2)); console.log(node + '');
console.time('A');
node.orderPrint();
console.timeEnd('A'); console.log('------------------------------------');
console.time('B');
node.orderPrint2();
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)) */ function MPNode() {
// 区分原子结点和表结点
this.tag = undefined;
// 指数域
this.exp = 0; // 系数域
this.coef = 0;
// 表结点的表头指针
this.hp = null; // 相当于线性表的next,指向下一个元素结点
this.tp = null;
}

javascript实现数据结构:广义表的更多相关文章

  1. 【C/C++】实现数据结构广义表

    1. 广义表的定义     每个元素可以为Atom,原子,也可以为线性表.      线性表的推广.线性表元素有唯一的前驱和后继,为线性表,而广义表是多层次的线性表      表头:第一个元素,可能是 ...

  2. 数据结构(C语言第2版)-----数组,广义表,树,图

    任何一个算法的设计取决于选定的数据结构,而算法的实现依赖于采用的存储结构. 之前线性表的数据元素都是非结构的原子类型,元素的值是不可再分的.下面学习的这两个线性表是很特殊的,其中数据元素本身也可能是一 ...

  3. 数据结构算法C语言实现(十九)--- 5.5&5.6&5.7广义表

    一.简述 传说Lisp的基本数据结构就是广义表,广义表也是具有典型递归属性的数据结构,此外,由于建表要处理字符串,用C语言处理起来也是一脸懵逼.....最后自己还想写一个将广义表还原成字符串的函数,一 ...

  4. 数据结构(C语言版)-第4章 串、数组和广义表

    补充:C语言中常用的串运算 调用标准库函数 #include<string.h> 串比较,strcmp(char s1,char s2) 串复制,strcpy(char to,char f ...

  5. 数据结构:广义表的实现(Java)

    广义表的简单理解在这篇博文中:https://blog.csdn.net/lishanleilixin/article/details/87364496,在此不做赘述. Java实现广义表: pack ...

  6. 数据结构28:广义表及M元多项式

    广义表,又称为列表.记作: LS = (a1,a2,…,an) ;( LS 为广义表的名称, an 表示广义表中的数据). 广义表可以看作是线性表的推广.两者区别是:线性表中的数据元素只能表示单个数据 ...

  7. 数据结构之---C语言实现广义表头尾链表存储表示

    //广义表的头尾链表存储表示 //杨鑫 #include <stdio.h> #include <malloc.h> #include <stdlib.h> #in ...

  8. 数据结构 c++ 广义表

    // CTest.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #include &l ...

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

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

随机推荐

  1. Directx11学习笔记【十一】 画一个简单的三角形--effect框架的使用

    这里不再介绍effect框架的具体使用,有关effect框架使用可参考http://www.cnblogs.com/zhangbaochong/p/5475961.html 实现的功能依然是画一个简单 ...

  2. 用bat 删除当前文件夹下的某类文件

    @echo on for /r %%f in (*.pdb,*.xml) do del %%f 保存为bat文件执行!

  3. webclient下载文件 带进度条

    private void button1_Click(object sender, EventArgs e) { doDownload(textBox1.Text.Trim()); } private ...

  4. sql 添加用户

    use master GO EXEC sp_addlogin 'infos1', '1', 'master' exec sp_grantdbaccess 'infos311' -- 给访问权限 USE ...

  5. iOS_67控件外观对照

    iOS 7 button无边框,操作栏透明,控制器全屏 Tab Bar 对照 iOS 7                                                         ...

  6. Android client和服务器JSP互传中国

    出于兼容性简化.传统中国等多国语言.推荐使用UTF-8编码. 首选.我们期待Android到底应该怎么办: 在发送前,应该对參数值要进行UTF-8编码,我写了一个static的 转换函数.在做发送动作 ...

  7. 使用JMX实现的内存监控(转)

    public final class MemoryWarningSystem { private static MemoryWarningSystem m_instance = null; /** * ...

  8. Android剪裁图片简单的方法

    /** * 按正方形裁切图片 */ public static Bitmap ImageCrop(Bitmap bitmap) { int w = bitmap.getWidth(); // 得到图片 ...

  9. 【转】Android 避免APP启动闪黑屏(Theme和Style)

    前几天Boss就反应说,机器每次启动程序都会闪一下黑屏,这个客户不接受.没办法,只能想想怎么解决,最后找到了下面的方法.闪黑屏的原因主要是我们启动Activity的时候,需要跑完onCreate和on ...

  10. HDU 1950 Bridging signals (DP)

    职务地址:HDU 1950 这题是求最长上升序列,可是普通的最长上升序列求法时间复杂度是O(n*n).显然会超时.于是便学了一种O(n*logn)的方法.也非常好理解. 感觉还用到了一点贪心的思想. ...