廣義表是線性表的推廣。普遍用於人工智能的表處理語言Lisp,把廣義表做爲基本的數據結構。node
廣義表通常記做:算法
LS = (a1, a2, ..., an)數據結構
LS是廣義表的名稱,n是它的長度,ai能夠是單個元素,也能夠是廣義表,分別稱爲廣義表LS的原子和子表。習慣上,用大寫字母表示廣義表的名稱,小寫字母表示原子。當廣義表LS非空時,稱第一個元素a1爲LS的表頭,稱其他元素組成的表(a2, a3, ..., an)是LS的表尾。ide
下面列舉一些廣義表的例子:函數
1.A = () ---- A是一個空表,它的長度爲0。ui
2.B = (e) ---- 列表B只有一個原子e,B的長度爲1。this
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)))。atom
5.E = (a, E) ---- 這是一個遞歸的表,它的長度爲2.E至關於一個無限的列表E = (a, (a, (a, ...)))。spa
下圖爲列表的圖形表示:
從定義和例子可推出三個結論:
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 }