用途是用$O(\sqrt{n})$的方式线段计算(线段树是二分$O(logN)$)
是不是和莫队很像2333
- 相当于(数组+链表)
- 网站part
基本操作:
(1)定位:先定位元素所在的链表节点,然后再定位该元素在数组中的位置。
(2)分裂:将某个链表节点分裂成两个节点。
(3)插入:首先定位要插入的位置,然后将所在节点分裂成两个节点,并将数据放到第一个节点的末尾。 如果要插入的是一大块数据,首先要将数据切成多个block(每个block对应一个块状链表的一个节点)并将这些block链起来,然后将它们插入那两个节点之间。
关键点和复杂度分析
该算法的核心是确定链表长度和每个节点的数组长度,以及怎么保证这个长度值?设块状链表中元素总个数为X,链表长度为n,每个节点中数据长度为m,则当m=n=sqrt(X)时,可保证m和n同时最小,此时各种操作的时间复杂度最低。在实际应用时,需维持块状链表的每个节点大小在[sqrt(n)/2, 2*sqrt(n)],否则,块状链表会退化。维护方法是,适当的时候,对节点进行合并与分裂(维护本身不会使复杂度增加)
- 论文part
而且块状链表非常好扩展,只要是序列操作,比如:统一赋值,翻转,求和,维护最小值等等,都可以使用块状链表得到的复杂度,而如果将整个块状链表维护成有序的,它甚至可以实现平衡树的一些操作[1],毕竟平衡树也可以看作是一种维护序列的方法。
又因为块状链表只在每个分块记录一些额外信息,它的空间利用率很高,而同是模拟方法的Splay需要在每个节点上维护全部额外信息,虽然速度比较快,却占用大量内存[2]。
其实,在日常生活中我们经常会用到块状链表:传统的FAT文件系统就是将磁盘扇区分簇,然后用FAT表(FileAllocation Table 文件分配表)来记录每一个簇的状态:是否损坏,是否被使用,如果被使用那么它的下一个簇是哪一个簇。可见,FAT文件系统的思想和块状链表是一致的。
而且因为块状链表空间利用率很高,分块的结构又能很方便的和缓冲区结合使用,Vim[3]也使用了块状链表,在内存的存储和在磁盘上的缓冲都使用了类似块状链表的结构[4]。试想如果用Splay去写一个文本编辑器会是多么复杂而抽象,它又如何方便地利用缓冲区,一旦发生崩溃、断电等意外事件,又如何从磁盘缓冲中重构树结构、恢复数据?
另外,已经有人在g++的
库中写了一个基本的块状链表模板:__gnu_cxx::rope ,也就是说,使用C++的同学可以很方便的得到一个现成的块状链表[5]。
[1] 我用块状链表做NOI2006happybirthday,略微比我写的SBT短一些,可以过8个点(第8个点接近时限)。
[2] 利用Splay可以把模拟操作的复杂度降到(其实这也是空间换时间的例子:线段树、Splay维护的额外信息多,空间占用大,但是速度也快),关于Splay的讨论可以参考2007年余江伟的集训队论文《如何解决好动态统计问题》,2004年杨思雨的集训队论文《伸展树的基本操作与应用》。
[3] Vi IMproved,应该算是很有名了吧。
[4] 就像BramMoolenaar所说:A texteditor is used all day by many people; it is worth investing time and effort inmaking it work well. Vim使用的数据结构很复杂,但是原理和块状链表是相似的。
[5] http://www.sgi.com/tech/stl/Rope.html上有一份rope
的文档。
例程 4+2 07
1 |
|
参考资料
- 中山大学程序设计竞赛(中山大学内部选拔赛) 真题解(一)
- 对块状链表的一点研究 苏煜