和线性表的链式存储结构相类似,也可采用链式方式存储串值。由于串结构的特殊性--结构中的每个数据元素是一个字符,则用链表存储串值时,存在一个“结点大小”的问题,即每个结点可以存放一个字符,也可以存放多个字符。
下面是结点大小为4(即每个结点存放4个字符)的链表:
head --> (a) --> (b) --> (c) --> ... --> (i)
当结点大小大于1时,由于串长不一定是结点大小的整倍数,则链表中的最后一个结点不一定全被串值占满,此时通常补上“#”或其它非串值字符。
为了便于进行串的操作,当以链表存储串值时,除头指针外还可附设一个尾指针指示链表中的最后一个结点,并给出当前串的长度,称如此定义的串存储结构为块链结构。
由于一般情况下,对串进行操作时,只需要从头向尾顺序扫描即可,则对串值不必建立双向链表。设尾指针的目的是为了便于进行连接操作,但应注意连接时需处理第一个串尾的无效字符。
在链式存储方式中,结点大小的选择和顺序存储方式的格式选择一样都很重要,它直接影响到串处理的效率。如果串很长,这要求我们考虑串值的存储密度:
存储密度 = 串值所占的存储位 / 实际分配的存储位
串值的链式存储结构对某些串操作,如连接操作等有一定方便之处,但总的来说不如另外两种存储结构灵活,它占用存储量大且操作复杂。
结构图:
实现代码:
1 function Chunk(chunkSize) { 2 this.chunkSize = chunkSize || 4; 3 this.ch = []; 4 for (var i = 0; i < this.chunkSize; i++) { 5 this.ch[i] = ‘#‘; 6 } 7 // type: Chunk 8 this.next = null; 9 } 10 11 exports.LString = LString; 12 function LString(chunkSize) { 13 // type Chunk 14 this.head = null; 15 // type: chunk 16 this.tail = null; 17 // 串的当前长度 18 this.length = 0; 19 this.chunkSize = chunkSize || 4; 20 } 21 22 LString.prototype = { 23 // 将字符串转换成LString类型 24 strAssign: function (chars) { 25 this.head = this.tail = new Chunk(this.chunkSize); 26 this.length = chars.length; 27 28 var current = this.head; 29 for (var i = 0, len = chars.length; i < len; i++) { 30 current.ch[i % this.chunkSize] = chars[i]; 31 if (i + 1 < len && (i + 1) % this.chunkSize === 0) { 32 current.next = new Chunk(); 33 current = current.next; 34 } 35 } 36 37 this.tail = current; 38 }, 39 // 字符串对比 40 // TODO 是否去掉chunkSize的对比 41 strCompare: function (tLString) { 42 var current = this.head; 43 var curT = tLString.head; 44 45 if (this.length !== tLString.length) return false; 46 47 while (current) { 48 for (var i = 0; i < this.chunkSize; i++) { 49 if (current.ch[i] !== curT.ch[i]) return false; 50 } 51 52 current = current.next; 53 curT = curT.next; 54 } 55 56 return true; 57 }, 58 clearString: function () { 59 this.head = this.tail = null; 60 this.length = 0; 61 }, 62 concat: function (tLSting) { 63 if (!tLSting.length) return; 64 65 var ret = new LString(this.chunkSize); 66 67 if (this.head === null) { 68 copyString(ret, tLSting); 69 } else { 70 ret.head = ret.tail = new Chunk(this.chunkSize); 71 copyString(ret, this); 72 73 var index = ret.tail.ch.indexOf(‘#‘); 74 if (index === -1) { 75 copyString(ret, tLSting); 76 } else { 77 copyString(ret, tLSting, ret.tail, tLSting.head, index); 78 } 79 } 80 81 return ret; 82 }, 83 substring: function (pos, len) { 84 pos = ~~pos || 0; 85 len = ~~len || this.length; 86 if (pos < 0 || pos > this.length - 1 || len < 0 || len > this.length - pos) 87 throw new Error(‘unexpected parameter‘); 88 89 var sub = new LString(this.chunkSize); 90 var current = findPosChunk(this, pos); 91 var curS = sub.head = new Chunk(this.chunkSize); 92 var i = 0; 93 sub.length = len; 94 95 outerloop: while (current) { 96 for (var j = 0, size = this.chunkSize; j < size; j++) { 97 if (i === len) { 98 break outerloop; 99 } else { 100 curS.ch[j] = current.ch[(i + pos) % this.chunkSize]; 101 i++; 102 if ((i + pos) % this.chunkSize === 0) { 103 current = current.next; 104 } 105 if (i % this.chunkSize === 0 && (current.ch[i] || current.next)) { 106 curS.next = new Chunk(this.chunkSize); 107 curS = curS.next; 108 } 109 } 110 } 111 } 112 113 return sub; 114 }, 115 toString: function () { 116 var current = this.head; 117 118 if (current === null) return ‘‘; 119 120 var str = ‘‘; 121 while (current) { 122 for (var i = 0, len = this.chunkSize; i < len; i++) { 123 var ch = current.ch[i]; 124 if (ch === ‘#‘) { 125 return str; 126 } else { 127 str += current.ch[i]; 128 } 129 } 130 current = current.next; 131 } 132 133 return str; 134 } 135 }; 136 137 function findPosChunk(lString, pos) { 138 var current = lString.head; 139 while (current) { 140 for (var i = 0, len = lString.chunkSize; i < len; i++) { 141 if (pos-- === 0) return current; 142 } 143 current = current.next; 144 } 145 } 146 147 function copyString(destination, target, curD, currT, offset) { 148 offset = offset || 0; 149 currT = currT || target.head; 150 curD = curD || destination.head; 151 var k = 0; 152 153 while (currT) { 154 for (var i = 0, len = target.chunkSize; i < len; i++, k++) { 155 var j = k % curD.chunkSize + offset; 156 curD.ch[j % curD.chunkSize] = currT.ch[i]; 157 158 if ((j + 1) % curD.chunkSize === 0 && (currT.ch[i + 1] || currT.next)) { 159 curD.next = new Chunk(destination.chunkSize); 160 curD = curD.next; 161 } 162 } 163 164 currT = currT.next; 165 } 166 167 destination.tail = curD; 168 destination.length += target.length; 169 } 170 171 var a = new LString(); 172 var b = new LString(); 173 var c = new LString(); 174 175 a.strAssign(‘abcdefg‘); 176 console.log(a + ‘‘); 177 b.strAssign(‘hijklmno‘); 178 console.log(b + ‘‘); 179 c.strAssign(‘abcdefg‘); 180 console.log(a.strCompare(b)); 181 console.log(a.strCompare(c)); 182 var t = a.concat(b); 183 console.log(t + ‘‘); 184 t = t.substring(2, 5); 185 console.log(t + ‘‘);
单元测试代码:
1 describe(‘LString tests‘, function(){ 2 var a = new LString(5); 3 var b = new LString(4); 4 var c = new LString(5); 5 var t; 6 7 it(‘should assign string‘, function(){ 8 a.strAssign(‘abcdefg‘); 9 expect(a + ‘‘).toBe(‘abcdefg‘); 10 11 b.strAssign(‘hijklmno‘); 12 expect(b + ‘‘).toBe(‘hijklmno‘); 13 14 c.strAssign(‘abcdefg‘); 15 expect(c + ‘‘).toBe(‘abcdefg‘); 16 }); 17 18 it(‘should compare‘, function(){ 19 expect(a.strCompare(b)).toBe(false); 20 expect(a.strCompare(c)).toBe(true); 21 }); 22 23 it(‘should concat‘, function(){ 24 t = a.concat(b); 25 expect(t + ‘‘).toBe(‘abcdefghijklmno‘); 26 }); 27 28 it(‘should substring‘, function(){ 29 t = t.substring(2, 5); 30 expect(t + ‘‘).toBe(‘cdefg‘); 31 }); 32 });