部分內容參考自 論文 蘇煜《對塊狀鏈表的一點研究》
1. 數組和鏈表對比:
操作 | 數組 | 鏈表 |
---|---|---|
存儲結構 |
地址連續的存儲單元,物理位置相鄰 |
地址不連續,物理位置不相鄰 |
定位 |
O(1) |
O(N) |
添加 |
O(N) |
O(1) |
刪除 |
O(N) |
O(1) |
數組有很好的定位功能,一般對應於固定的長度,不適合添加/刪除等操作,當數組有序時,可二分查找某值,效率很好,O(logN)。
鏈表添加刪除效率極高,很適合這類操作。但定位效率很低。
2.塊狀鏈表
塊狀鏈表是對數組和鏈表的折中,集兩者之長,在定位,添加刪除的效率上都有大幅提升。
整體上使用鏈表,鏈表節點是一個大小適當的數組。
如下圖:
3.基本操作
①:定位
從鏈表頭開始往後掃,每個節點記錄本節點合法數據的長度,最終會定位爲某一個塊及塊內偏移。
②:分裂
將指定的塊在指定的位置分裂成2個塊。
③:合併
本塊合併掉之後的那一塊,前提是2塊的有效數據長度之和 <= N (array的長度)
在定位,插入,刪除的時候對本塊進行合併將會減少塊元素過少,否則有可能退化成普通的鏈表。
④:插入
在指定位置分裂,然後在本塊後面插入若干個塊,示意圖如下:
⑤:刪除
在指定的位置分裂,刪除本塊之後的若干塊,示意圖如下:
4.效率分析:
設數組大小爲x,數據總數爲N,則理想狀況下分塊樹爲N/x,則定位的時間複雜度爲O(N/x),插入刪除的時間複雜度爲O(x)
令N/x = x , x = sqrt(N),即每次操作的時間複雜度大致爲O(sqrt(N))
和平衡樹O(logN)等相比還是有較大差距,但是其附加空間很少,僅爲O(sqrt(N)),平衡樹的附加空間爲O(N)。
5.幾道例題:
①:NOI2003 editor(經典)
【題目大意】
一些定義:
文本:由0個或多個ASCII碼在閉區間[32, 126]內的字符(即空格和可見字符)構成的序列。
光標:在一段文本中用於指示位置的標記,可以位於文本首部,文本尾部或文本的某兩個字符之間。
文本編輯器:爲一個包含一段文本和該文本中的一個光標的,並可以對其進行如下六條操作的程序。如果這段文本爲空,我們就說這個文本編輯器是空的。
操作名稱
輸入文件中的格式
功能
MOVE(k)
Move k
將光標移動到第k個字符之後,如果k=0,將光標移到文本開頭
INSERT(n, s)
Insert n¿
S
在光標處插入長度爲n的字符串s,光標位置不變,n ³ 1
DELETE(n)
Delete n
刪除光標後的n個字符,光標位置不變,n ³ 1
GET(n)
Get n
輸出光標後的n個字符,光標位置不變,n ³ 1
PREV()
Prev
光標前移一個字符
NEXT()
Next
光標後移一個字符
比如一個空的文本編輯器依次執行操作INSERT(13, “Balanced tree”),MOVE(2),DELETE(5),NEXT(),INSERT(7, “ editor”),MOVE(0),GET(16)後,會輸出“Bad editor tree”。
你的任務是:
建立一個空的文本編輯器。
從輸入文件中讀入一些操作並執行。
對所有執行過的GET操作,將指定的內容寫入輸出文件。
ps:這些操作僅僅是定位,添加,刪除,採用塊狀鏈表可以輕鬆搞定,splay等各種也可以。
有興趣的可以在下oj上測試下自己的代碼:
②:反轉序列
【題目大意】
一個長度爲 n 的整數序列初始時從左到右爲1,2,3,……,n,現在對這個序列進行 m 次操作,每次把 p 到 q 的子序列反轉 求最後的序列ps:操作很簡單,每次對一個區間進行反轉,求若干次反轉之後的序列。
這裏需要在每個節點維護一個域rev,rev=true代表本區間需要反轉,false表示不需要反轉
在區間合併、分裂以及最後的輸出上需要維護rev,操作很簡單,反轉該區間的值即可。
在反轉若干的區間的時候問題等價於將鏈表反轉。
有興趣的可以在下oj上測試下自己的代碼:
http://cstest.scu.edu.cn/soj/problem.action?id=3035
ps:用一個數組維護每一塊的長度,查找時可以二分,添加、刪除時採用順序操作。
刪除是二分定位可以跳過中間的直接刪掉所有待刪除塊。
因爲移位效率低,用平衡樹來維護就捨本逐末了,目測效率也沒多大改進。
不過對於反轉序列來說,因爲不涉及添加刪除,效果還是可觀的。