为了弥补链表在内存分配上的不足,出现了静态链表这么一个折中的办法。静态链表比较类似于内存池,它会预先分配一个足够长的数组,之后链表节点都会保存在这个数组里,这样就不需要频繁的进行内存分配了。
当然,这个方法的缺点是需要预先分配一个足够长的数组,肯定会导致内存的浪费。数组不够长到不是什么大不了的,使用第一节的动态扩容方法就是了。
静态链表一般是由两个链表组成,一个保存数据的链表,一个空闲节点的链表,如图 3 所示。
图 3 静态链表
当需要向链表中添加节点时,就从空闲链表中摘下一个使用。从链表中删除节点时,就将被删除的节点归还到空闲链表中。
在实现上,由于静态链表的节点都是存储在数组中的,所以经常使用数组索引代替指针,如果数组扩容了,也不会影响现有的节点。下面简单的实现了一个静态双向链表,没有添加动态扩容的能力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | struct snode { int value; int prev; int next; }; struct sllist { snode *nodes; int head, freeHead; sllist():head(-1), freeHead(0) { // 初始化空闲链表,静态分配长度为 100。 nodes = new snode[100]; for ( int i = 0;i < 100;i++) { nodes[i].next = i + 1; } } void add( int value) { // 从空闲链表中摘取节点。 int newNode = freeHead; freeHead = nodes[freeHead].next; nodes[newNode].value = value; nodes[newNode].prev = -1; nodes[newNode].next = head; if (head != -1) { nodes[head].prev = newNode; } head = newNode; } void remove (snode node) { int idx = head; if (node.prev == -1) { head = node.next; } else { idx = nodes[node.prev].next; nodes[node.prev].next = node.next; } if (node.next != -1) { nodes[node.next].prev = node.prev; } // 将节点归还空闲链表。 nodes[idx].next = freeHead; freeHead = idx; } }; |
静态链表的效率几乎跟数组一样,极大的提升了链表的效率。不过因为链表的效率受内存分配影响,不同的语言可能有不同的表现,具体情况还需要实验分析才可以。