數據結構總結

常見的數據結構運用總結

考慮到Obsidian三個成員的擅長領域,這段時間都在做雜題,算是學習各種算法吧,趁現在休息的時間,而且大家馬上要備戰今年的比賽了,寫寫自己專攻方面的一些心得吧

扯開線段樹、平衡樹這些中高級的東西,先說說基礎的數據結構



算是代碼量最小的數據結構?出棧進棧都只有一句話而已

常見用途:
消去一個序列中的相同元素(做法大家應該都知道了吧,見過很多次了)
維護一個單調的序列(所謂的單調棧,dp的決策單調?)
表達式求值(經典的棧運用,如果使用的很熟悉的話,可以處理一元、二元運算,不過最近沒見過類似的題目了)
用於輔助其他算法(計算幾何中的求凸包)

隊列

隊列應該還是很常見的數據結構了,如果專攻圖論的話,spfa應該是寫爛了的
這裏說到的隊列,是狹義的普通的隊列和循環隊列,不包括後面講的一些變形
注意循環隊列的寫法,儘量不要使用取模運算,不然的話,遇到不厚道的出題者,可以把取模的循環隊列卡到死

常見用途:
主要用於輔助其他算法,比如說spfa,bfs等(建議習慣用stl的孩子手寫queue,畢竟就幾行代碼而已,偷懶會付出代價的。。。)

雙端隊列

如果寫dp寫的多的話,這個東西應該還是算是比較基礎的東西了,雙端隊列多用於維護一個滿足單調性的隊列
還是建議手寫,stl的deque使用塊狀鏈表寫的,那東西的複雜度是O(Nsqrt(N))的,不要被迷惑了。

常見用途:
dp的單調性優化,包括單調隊列優化和斜率優化,都要用到這個結構
計算幾何中的算法優化,比如半平面交
樹的分治問題中利用單調隊列減少轉移複雜度

鏈表 Dancing Links

寫圖論的不要告訴我不會寫這貨,鏈表可以寫單雙向,循環非循環的,高級點兒的可以考慮十字鏈表,麻花鏈表
不過鏈表可以說是樹形結構的基礎,如果這個掌握的不好,那麼樹形結構寫起來就會很糾結
鏈表的優勢在於可以O(1)的插入刪除,如果要求插入的位置只是在序列的兩端的話,這個數據結構是最方便的了(無視雙端隊列)
hash表就是用鏈表實現的,熟悉hash的同學可以試試看怎麼使你的hash效率提高

常見用途:
圖的鄰接表,維護不確定規模的二維數組
約瑟夫問題,複雜度O(NlogN)
單純考鏈表的題目,比如說類似文本編輯器的東西
算法實現上輔助,比如說POJ3183
搜索優化,最經典的就是Dancing Links
維護路徑,嗯,這類問題就比較噁心了,有兩題都是類似的問題,題的名字都叫做植物大戰殭屍……

字典樹 自動機

字典樹又稱爲前綴樹,用於維護一系列串(不一定是字符串,可以把一個數字看成二進制串,k進制串等),我們可以通過字典樹解決很多關於串的問題

常見用途:
字符串匹配(最基礎的用途了吧)
字符串hash(利用字典樹可以在O(N)的時間內查出一個串是否在集合中,N是串的長度)
構建dp轉移矩陣(其實很多題目都是利用自動機構建轉移矩陣,然後矩陣快乘的,這也算是比較基礎的運用了)
字符串在自動機上的dp(這個是很大的一塊,算是自動機最大的運用點)

堆 優先隊列

堆在這段時間運用的次數明顯增加了很多,自從某學校出了那計算幾何掃描線之後,大家對這個數據結構不像原來那麼陌生了
普通堆代碼量在20到30行左右,如果需要支持更改權值,刪除特定結點的操作的話,需要改點堆,代碼會多那麼10行左右
大家對於堆的最早理解,可能就是堆優化dij了,這個也算是它的一個很重要的運用吧

常見用途
堆優化dijkstra(很經典的運用)
極角掃描線(詳見Visible Segments)
某些貪心的題(我記得ZOJ月賽出過好幾次的?CF也有類似的題目,還有一題經典的倒水問題HDU1692)
堆優化的prim(已經很久沒見過非要這麼寫的題目了,最經典的問題是最優比例生成樹)
搜索中的優化(大家都懂得,A*算法基於優先隊列)
滿足偏序關係的集合(其實極角掃描線算是這個的一個子類了,很多問題可以轉化過來)

並查集

這個東西應該在關於樹的問題中非常常見了,用在非常多關於集合的合併問題上

常見用途:
離線LCA(tarjan算法的精髓就是並查集)
集合維護,比如說POJ的食物鏈和我們OJ的Food,這個算是擴展並查集的經典運用之一

幾類可以用基礎數據結構解決的經典樹上統計問題
求一顆樹中任意兩點間的距離,我們可以離線LCA,利用並查集維護點到父親結點的距離,每次找到LCA之後,在LCA結點拉一個處理鏈表,回溯到LCA的時候處理全部以這個點爲LCA的所有查詢。
靜態查詢一棵樹某條路徑上的最大最小邊權,利用並查集維護點到其父親結點這條路徑上的最大邊權即可。
給你一棵樹,叫你求一條有向路徑上前面一個點減去後面一個點的點權差值最大,依然是擴展並查集,怎麼實現大家自己想吧。
還有一題是HDU的3804,很經典的題目,這題可以用四種不同的方法過,大家有興趣的可以YY一下。

基礎的數據結構告一段落,下面主要講講線段樹和樹狀數組的運用吧,直接說常見用途了,畢竟這個大家用的太多了

常見用途(不擴展):
dp優化(非常多的dp問題可以用樹狀數組和數據結構來優化,比如說我們校賽初賽和決賽的某題,dp專題的某題,還有LIS的O(NlgoN)優化等等)
括號序列、樹的線性序列統計問題(之前發過了,就不擴展了)

更新點查找區間,更新區間查找點
這個經典問題都是可以利用樹狀數組來做的,可以說樹狀數組太強大了,代碼短常數小,如果遇到此類的問題,那就是裸的不能再裸了,專攻數據結構的,需要對這類問題的經典模型儘量多的進行掌握。
這類問題最常見的用處就是求逆序對(HDU1394)和求一個區間內一堆直線相交的個數(保證不會有三條直線交於一點,例子ZOJ3157)
URAL1470,三維樹狀數組,其實和二維沒啥區別,就是要抓住如何更新
樹狀數組的經典運用有兩題經典題,一個是POJ2828,一個是HDU3436,後者是前者的動態版,如果可以自己想出這兩題的做法,基本上基礎的樹狀數組題目是沒有問題了。

更新區間查找區間
這類問題一般是不能使用樹狀數組來做的(那啥用差分求區間和的無視,那種只是特例而已),一般正式比賽考察的比較多的就是這類問題,非常重視對懶操作的實現,一般建議大家寫一個上傳和一個下放函數,即使是就一行,寫到函數中,擴展起來可以更方便。
如果想鍛鍊自己對數據結構的掌握程度,可以嘗試利用樹狀數組和線段樹來做Memory Control這題,如果能想出三種或者三種以上的狀態定義方法,就基本上合格了。
另外還有一題非常好的考察基本功的題目,是HDU1199,一般的離散化是會有問題的,就看大家怎麼寫了。
對於懶操作考察的另外一題是POJ3225,算是比較經典的區間翻轉的範例了。
如果以上的題目都解決了,就去寫zhymaoiing的Rain in the ACStar吧,我們OJ就有,這題寫了 基本上對計算幾何和數據結構的綜合就有感覺了。
最後建議大家去寫寫SPOJ的GSS系列和QTREE系列,這兩個系列在後面會專門提到。

掃描線
這種問題主要遇到的模型有以下幾種,括號中是對應的時間複雜度
給你N個矩形,求矩形的並面積(O(NlogN))
給你N個矩形,求矩形的並周長(O(NlogN))
給你N個矩形,每一個矩形有一定的權值,權值最多有K種,求加權的面積並,其中重疊部分用最大權值計算(O(NKlogN))
給你N個矩形,叫你求被覆蓋恰好K次或者至少K次的面積(O(NKlogN))
給你N個矩形,每一個矩形有一定的權值,權值最多有K種,求加權的面積並,其中重疊部分用特定的公式計算混合權值(O(2^K*NlogN))
給你N個點,用一個矩形覆蓋最多的點(O(NlogN))
給你N個點,M個矩形,詢問每一個點是否被包含在任意一個矩形中(O((N+M)log(N+M)))
給你N個點,點分爲可選點和不可選點,詢問用一個矩形覆蓋的最多可選點的數量,任何不可選點都不能被覆蓋,詢問覆蓋最多點的方案數(O(NlogN))

*樹鏈剖分 QTREE
這個是很大的一塊,但是也是很噁心的一塊內容,如果你覺得自己基礎的問題都沒有了的話,可以考慮兩個選擇,一個是學習這個,另外一個就是做做GSS
熟練剖分詳看漆子超的論文,具體實現就不講了,常見的題目,最經典還是SPOJ的QTREE系列了,這個對代碼能力的要求還是很苛刻的,在比賽中,如果遇到這類問題,如果又沒有強大的代碼能力,又沒有足夠的時間,那就不要去碰它吧。此外剖分推薦GSS7,經典題之一。
我做過的剖分題的列表:SPOJ QTREE1-4 HDU3601 POJ3237 ZJOI2008樹的統計 SPOJ GSS7 HDU3804(如果想寫就寫吧,這題不用剖分的) HDU3966(依然是一道可以不用剖分的題目)

*GSS系列解釋
GSS系列是SPOJ的經典線段樹題了,其中1、3、5是基礎的題目,建議大家在學習了線段樹後作爲強化用,GSS2和GSS7,其中GSS2是一個非常經典的模型,建議大家花一週的時間慢慢啃,如果自己思維好的話,有可能一個下午想出做法,不過那個題不是想出來就能寫出來的。。。GSS7是一個剖分,算是裸的題目了,就是維護的量太多,建議大家代碼能力提高之後再來寫寫。GSS6不是基礎的數據結構題,我將在後面提到這題。

以上的數據結構可以覆蓋區域賽的中低檔題,其中GSS2如果在區域賽中,完全可以算是難題了(前提是沒見過此類問題),對於區域賽中,這些數據結構可以說佔了90%以上

下面是一些偏們的數據結構和一些高級數據結構的介紹

劃分樹

劃分樹是一個可以在O(NlogN)時間內求出每一個子區間內第K小的數的數據結構,可以算是模板化的東西了,經常和樹狀數組結合來考察,不過考的不多,所以建議如果專攻數據結構的可以考慮學習一下,但是不要太過於鑽難題。

左偏樹 斜堆

兩類經典的可合併堆,可以在O(NlogN)時間內對數集進行合併,維護最值,建議學習一個,不建議都學習,我覺得沒必要。

塊狀鏈表

可以說在Splay沒有出來的年代,這個是萬能的武器,在O(Nsqrt(N))的時間內可以進行序列的翻轉、查詢操作,可以算是非常無敵了,但是在Splay出來了之後基本上就被遺棄了,不過塊狀鏈表的思想是非常好的,CF經常考察利用塊狀鏈表做的題目,要學習的話,就專門去看看其思想吧

跳錶

這個利用鏈表進行優化的數據結構,基本上沒啥用處,不過可以提提,時間複雜度是均攤的,沒有發現一定要這個數據結構解決的問題

平衡樹

平衡樹可以做幾乎所有關於數集的維護操作,常見的平衡樹有SBT和Splay,一般的話,建議如果專攻數據結構的可以在鞏固了基礎數據結構的基礎上,學學這兩種平衡樹。對這兩種結構進行比較,Splay的功能和SBT比起來,強大了不止一個檔次,但是相應地,其代碼量也大了不止一個檔次。按照我的代碼風格,SBT的代碼一般維持在150行左右,但是非要Splay來寫的題目,一般代碼量會超過200行,甚至超過300行。
如果是對數據集合進行維護,我們一般考慮使用SBT,因爲其編碼十分方便,常數相對也小了很多,所以首選是這個。相反的,如果涉及到維護序列的問題,那麼就需要使用Splay來維護了。
記住一點,那就是好的代碼風格,對數據結構的專攻者來說,是非常重要的,相同的結構,有的人需要300行,而且查代碼十分不方便,好一點的寫法,可以在150行左右解決。對於最簡單的SBT問題,一般代碼量可以保證在100行左右,那麼也就差不多了。畢竟代碼寫的亂的話,寫掛了就不好查了,隊友更沒辦法幫你差,這樣就會悲劇。

SBT相關的問題,最基礎的是樹的遍歷和數集的統計問題,比如說舉個例子:
給你10^9個數,有兩種操作,一個是查詢第K小的數是多少,一個是刪除第K小的數,很明顯,這種動態的問題是不能使用離散化加樹狀數組來實現的,因爲刪除的元素也是不確定的。
看到這題第一眼的反應,樹狀數組,但是發現刪除的也是第K小,那麼我們就只能使用平衡樹了,注意我們肯定不能把10^9個數加入到集合中,這樣會MLE的,具體的做法自己可以想想,其實和前面提到的Queue Jumper那題類似,需要一個轉化。

常見的SBT操作,需要掌握的有以下幾個:
數集相關的操作:
插入刪除元素,查詢一個比K小的數的個數,查詢樹中第K小的數,查找元素的前趨後繼,查詢一個數在集合中出現的次數,刪除第K小的數,查詢最值。
序列相關的非動態操作(不需要查詢區間或者做區間翻轉等高級操作)
把一個元素插入到指定位置,刪除第K個位置的元素。

對於Splay,我想說的是,這個數據結構運用的地方實在太多了,基本上其他平衡樹可以做的,它都可以,但是它可以做的,其他平衡樹不一定可以做。去年天津賽區的現場賽還有福州賽區的網絡賽都考到了Splay的運用,這種題目在區域賽中一般難度都比較大,平時的話,多寫寫就當作鍛鍊代碼能力吧。
這裏列舉一些Splay可以做的事兒,其中SBT可以完成的我就不列舉了。
給你一個雙關鍵字的集合,求滿足第一關鍵字在[l,r]區間內的結點的最小關鍵字(Parking Log),維護一個序列,求[l,r]的和、最大連續子字段和,對[l,r]區間進行翻轉操作(維護數列),叫你維護一個可合併可拆分的並查集,其中拆分是一個元素從一個集合轉移到另外一個集合(LRJ的數據結構專場,DS@HUST,當然這題可以不用Splay),給你一些結點,現在有合併兩個集合,查詢集合的最值、第K大操作(四川省OI2011棘手的操作,2011天津賽區Graph and Queries)。

此外我們可以把Splay運用到樹鏈剖分中去,利用Splay進行路徑剖分,用splay forest來維護路徑,這樣可以在O(NlogN)時間內維護一棵樹,甚至是一個森林所有樹的特性,這就是經典的動態樹問題,如果想了解的,可以問我,不過前提是把基礎的啃透了:)

平衡樹的資料還是很多的,推薦陳啓鋒的《Size Balanced Tree》以及某論文《利用伸展樹解決維護數列問題》,對於動態樹的論文,個人認爲中文版的都是不能看的,推薦Tarjan的lecture。

對於數據結構的其他方面
高級數據結構需要的是一個自己非常熟悉的模板(前期還是要靠模板活的,熟悉之後的話另當別論了,當然比賽帶上模板可以心安),不過對於數據結構,個人不推薦貼模板,這個如果養成了習慣的話,代碼能力是絕對上不來的,如果學了高級數據結構的話,建議每次有題寫的時候自己手敲一下,就當培養感情吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章