和线性表的链式存储结构相类似,也可采用链式方式存储串值。由于串结构的特殊性--结构中的每个数据元素是一个字符,则用链表存储串值时,存在一个“结点大小”的问题,即每个结点可以存放一个字符,也可以存放多个字符。
下面是结点大小为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 + '');