和線性表的鏈式存儲結構相相似,也可採用鏈式方式存儲串值。因爲串結構的特殊性--結構中的每一個數據元素是一個字符,則用鏈表存儲串值時,存在一個「結點大小」的問題,即每一個結點能夠存放一個字符,也能夠存放多個字符。ide
下面是結點大小爲4(即每一個結點存放4個字符)的鏈表:oop
head --> (a) --> (b) --> (c) --> ... --> (i)單元測試
當結點大小大於1時,因爲串長不必定是結點大小的整倍數,則鏈表中的最後一個結點不必定全被串值佔滿,此時一般補上「#」或其它非串值字符。測試
爲了便於進行串的操做,當以鏈表存儲串值時,除頭指針外還可附設一個尾指針指示鏈表中的最後一個結點,並給出當前串的長度,稱如此定義的串存儲結構爲塊鏈結構。this
因爲通常狀況下,對串進行操做時,只須要從頭向尾順序掃描便可,則對串值沒必要創建雙向鏈表。設尾指針的目的是爲了便於進行鏈接操做,但應注意鏈接時需處理第一個串尾的無效字符。spa
在鏈式存儲方式中,結點大小的選擇和順序存儲方式的格式選擇同樣都很重要,它直接影響到串處理的效率。若是串很長,這要求咱們考慮串值的存儲密度:prototype
存儲密度 = 串值所佔的存儲位 / 實際分配的存儲位指針
串值的鏈式存儲結構對某些串操做,如鏈接操做等有必定方便之處,但總的來講不如另外兩種存儲結構靈活,它佔用存儲量大且操做複雜。code
結構圖:blog
實現代碼:
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 });