关于算法
1.算法,不止于刷題
提到算法,不管是科班出身還是半路出家的程序員可能都會說上幾句,算法誰沒學過誰不知道啊?對于走工業界路線而非學術路線的同學來說,算法學習的最大作用也許是找工作…… 畢竟工作后,絕大多數時候都用各種成熟的類庫,少有自己實現高級數據結構和算法的時候。但剛結束一學期修的算法課,上得我還真跟沒學過算法似的,讓我大開眼界,雖然每次課上我都聽的不是很懂,但每節都期盼著老師又能帶來什么新奇的東西。一點點發現,原來竟然還有這么多很有用卻從來沒學習過甚至沒聽過的算法和數據結構啊!
這些可以算作比較前沿的研究成果了,而且又不是離我們的日常工作非常遙遠的純理論,所以個人覺得這些知識非常值得學習,哪怕只是了解一下開拓思路。參考資料的話,除了下面每節提到的具體論文外,系統介紹這些高級算法和數據結構的書還真是鳳毛麟角,不過還真發現了一本:Peter Brass的《Advanced Data Structures》。此外的話,就是老老實實地看原作者或分析得比較好的論文,這也是這學期練成的一項能力。當然,有的論文挺難的,我們可以節選著看,大多數時候還是看的挺享受的!
2.隨機化算法(Randomized Algorithms)
隨機化(Randomization)是這門課的重點,也是以前一直忽略了的,現在卻發現非常重要的算法設計策略。在《Introduction to Algorithm》(這門課的教材)中有一些系統的講解和舉例,但這本經典教材畢竟不是專門講隨機化算法的。專攻于這一方面的經典書籍是Motwani Raghvan的《Randomized Algorithms》,以及稍微現代化一些的《Probability and Computing: Randomized Algorithms and Probabilistic Analysis》,但對于我來說,這兩本真的都太難了!如果像我一樣只是有些感興趣,想稍微系統了解一下的話,推薦認真學習一下《Algorithm Design》中第13章隨機化算法就可以了,這一章內容從基本定義、w.h.p.、到常見隨機化算法和數據結構的分析,已經足夠詳細了~
2.1 Randomized Quicksort
就像歸并排序的關鍵在Merge函數一樣,快速排序性能的關鍵就在Partition函數。但因為我們無法控制輸入數據的樣子,所以這時一般求助于隨機化思想。隨機化快速排序采取的策略有兩種:1)隨機選取Pivot;2)隨機采樣三個數取中位數。兩種方式都能達到O(nlogn)。第一種方式的證明在CLRS中有詳細解釋,第二種方式在書中是以Problem習題形式出現的。當時在課上老師直接將這個看似挺難的問題分析,輕描淡寫地轉化到了擲硬幣模型中,四兩撥千金,給人感覺很震撼!
推薦閱讀:這兩種方式在《Introduction to Algorithm》中有詳細的性能分析,也可以通過《Algorithm Design》補充學習,里面的證明方法能稍微容易理解一些。
2.2 Skip List
因為之前工作中重點研究過Redis的緣故,所以對Skip List有些了解,Redis中Dictionary數據結構就是用Skip List而沒采取紅黑樹的方式實現的。當時了解是因為Skip List實現簡單、緩存locality好等,但不清楚原來跳表是一種隨機化數據結構。每個新插入的Key是通過反復投擲硬幣(Flip a fair coin)來決定是否將其Promote到向上一級。怎么樣,挺有意思的吧!但奇怪的是,很多算法書里都對Skip List只字未提,包括后面要講到的在大型分布式系統中非常重要的Bloom Filter。
推薦閱讀:
作者論文:《Skip Lists: A Probabilistic Alternative to Balanced Trees》
實現:Sedgewick的《Algorithm in C, 3rd edition》中13.5節。
基于CLRS的MIT課件對Skip List進行了補充,講解的也非常棒(強烈推薦!)!
2.3 Universal Hashing
通常來說,一個精心選取的哈希函數是可以良好工作的,但在一些極端情況下,例如有人知道你的哈希機制,并故意制造大量哈希沖突時就行不通了。這并不是天方夜譚,2012年時就真的發生過,包括PHP、Java、Python等幾乎所有主流平臺都中招了,詳細請看《Hash Collision DoS問題》。所以一種防御方法就是,不只使用一個哈希函數,而是一族函數,每次需要使用時都這一族函數中隨機選取一個。
推薦閱讀:
《Introduction to Algorithm》中Universal Hashing和Perfect Hashing兩節。
基于CLRS的MIT課件講解的也非常棒!
2.4 Bloom Filter & Cuckoo Filter
以前在《大數據:互聯網大規模數據挖掘與分布式處理》一書中曾聽說過Bloom Filter,但一直以為是一個很高級很難理解的算法。經過老師詳細分析講解后發現,原來這么簡單,而且真的太有用了!它就像我們日常使用的無處不在的哈希表一樣,可以與許多數據結構組合起來使用。其核心就是:在判斷數據是否存在于集合的問題中,如果否定答案占絕大多數,那么它將使用很少的空間幫你實現目的。偶爾碰到肯定答復時,BF給出的只是不確定(False positive),這時就要去查BF背后的真正數據了。
而Cuckoo Filter是CMU的一位中國同學實現的,它巧妙地運用Fingerprint、Multi-choice哈希等技術,主要解決BF的痛點:無法刪除或擴展(resize),只能推倒重建,以及數據locality不好,會大量產生隨機I/O。
推薦閱讀:
論文(強烈推薦!):《Theory and Practice of Bloom Filters for Distributed Systems》
論文:《Cuckoo Filter: Practically Better Than Bloom》
2.5 Robin-Karp String Matching
關于字符串匹配,老師沒有講更有名的KMP算法,也是看中了Robin-Karp算法中的隨機化思想:充分利用已經遇見過的“字符”形成Rolling哈希,減少浪費(這也是很多高效算法的常用策略,例如Trie樹也是如此)。而且對于匹配的判斷與前面介紹過的Bloom Filter有些類似,也會產生False positive。CLRS中給出了詳細的講解和證明。
推薦閱讀:《Introduction to Algorithm》中對應章節。
2.6 Treaps
就像我們用兩種版本的隨機化保證Quicksort的性能一樣,Treaps是對二叉搜索樹的“保護”。它通過給每個Key額外隨機分配一個Priority數,插入新數據時會讓樹根據Key保證BST的性質,同時根據隨機分配的Priority保證堆特性,達到樹的平衡。所以從名字就能看出,Treap=Tree+Heap。
推薦閱讀:《Introduction to Algorithm》中是作為Problem習題出現的。
2.7 Matrix Product Equlity Testing
這是當時老師留的作業里的一道題,很神奇的一道題,推薦大家看一下,真的可以從中看出隨機化的魅力!證明方式與Universal Hashing一樣,采用了所謂的Deferred Principle。具體我也不太懂,大意是固定住其他所有變量的值只保留一個“自由”變量,得出這種特殊情況的結果后再看這種特殊情況有多少種(即剛才固定住的那些變量有多少組合方式),從而簡化了證明。感覺有些難,等有時間要系統學習一下概率問題的證明方法。
推薦閱讀:
《Matrix-Product Verification》
《Verifying matrix multiplication》
Deferred Principle:《Events and Probability (verifying matrix multiplication, randomized min-cut)》
3.并行算法(Parallel Algorithms)
并行計算也是當前的一個熱點,作為初學者,我們最好的資料就是《Introduction to Algorithm》。它首先介紹了Work、Span、Parallelism三個基本概念和相關定理,簡單來說Work就是單核運行時程序的性能,而Span就是在有無限核心的平臺上運行程序時的性能,最后兩者的比例就是Parallelism,即程序的最大并行度。要實現并行化主要有三個原語:Parallel-loop、Spwan、Sync。書中重點分析了矩陣相乘和歸并排序,這兩個很常見算法的并行化版本。
4.外存算法(External Memory Algorithms)
盡管RAM模型是我們最熟悉的模型,但它并不是萬能的,例如在分析外部存儲相關算法時就有些無能為力。因為與外部存儲的I/O延遲相比,RAM分析的內存操作漸近復雜度可以忽略不計了。正出于這個原因,在考慮外部存儲時,我們會采用另一種分析模型——DAM。重點分析與磁盤的數據交換次數,即I/O數。
我們可以把最常見算法的數據結構放到DAM模型中去分析,例如線性搜索、二分查找,還有最重要的K-Way合并算法(在如數據庫的JOIN等有廣泛應用)。找到些感覺后,再分析復雜一些的矩陣相乘、B-Tree、LSM-Tree、B^epsilon-Tree等。
因為之前只了解B-Tree與外存有關,當系統學習到許多外存算法時還以為這是個比較新的話題。實際卻是,像Hash表的Linear Probing早在上世紀70年代,Knuth就在《The Art of Computer Programming》(TAOCP)中有詳細的研究,還真是孤陋寡聞了!
推薦閱讀:
Pagh整理的手冊(強烈推薦!):《Basic External Memory Data Structures》
StonyBrook課件:《Analyzing I/O and Cache Performance》
5.寫優化數據結構(Write-Optimized Dictionaries, WODs)
寫優化可以看做是一種很重要的設計策略,即Buffer緩沖區的思想。問題背景就是B-Tree家族的普遍問題,每次插入都會將新加入Key放置到最終位置,從而導致插入性能瓶頸。而WODs家族的數據結構使用緩沖思想,將樹內部結點中的一部分劃出來當作緩沖區,當插入數據時先暫存到緩沖區,之后再一點點Flush到Key應去的位置。WODs家族的成員有Buffer Tree(這應該是應用這種策略比較早也比較原始的一種樹了)、谷歌的應用了緩沖和級聯兩種思想的Log-Structured Merge Tree (LSM-Tree,廣泛應用于HBase、Cassandra、LevelDB等流行軟件)、StonyBrook老師研究出的B^epsilon-Tree和Cache-Oblivious Streaming B-trees(COLA)等變種。
重點說一下B^epsilon樹。它將Key抽象成異步消息,整個樹仿佛成了一個MQ。根據對epsilon參數的選擇,決定每個樹結點中要留出多少空間作為Buffer。不僅對B-Tree的寫性能有大幅提升,而且能夠充分利用磁盤帶寬。值得一提的是,幾個老師合伙將B^epsilon-Tree應用到數據庫、Linux文件系統等各種存儲軟件中,于是就商業化成了一個公司。簡單說就是,一種數據結構撐起來了一家創業公司,完全由技術驅動來發起,真是令人欽佩!
推薦閱讀:
論文:《The Buffer Tree: A Technique for Designing?
Batched External Data Structures》
論文:《The Log-Structured Merge-Tree (LSM-Tree)》
論文(強烈推薦!):《An Introduction to Bepsilon-trees and Write-Optimization》
論文:《Cache-Oblivious Streaming B-trees》
從MIT過來的大牛Bender教授(強烈推薦!),其中幾乎涉及了大數據和WODs的方方面面,并有一些對他商業化公司的介紹,Slide內容非常棒!:《Data Structures and Algorithms for Big Databases》
5.CO算法(Cache-Oblivious Algorithm)
這是一類神奇的算法,在任何Block大小的機器架構上都能以最優的性能運行!這在前面介紹的DAM模型中是不可想象的,因為分析外存算法時都要假設一個Block大小B,然后在此基礎上進行分析。對于任意大小的B或者說忽略B,真是難以想象!但這樣的算法真的存在,而且不只是一個兩個,已經發現了好多。
CO算法的模型叫做理想緩存模型(Ideal Cache Model),在DAM模型基礎上,假設了內存大小M遠大于B,完美的Pager進行Swap等條件。雖然這個模型類似DAM,只是兩層緩存架構,但是可以證明在現代多層緩存架構(CPU寄存器-L1-L2-L3-Memory-Disk)上也是最優的。已知的CO算法例如矩陣轉置、矩陣相乘等等,具體請參考下面推薦的閱讀資料。
要設計或改造出CO算法或數據結構可不是那么容易,但也是有方法可循,論文中列舉了三個常用策略:van Emde Boas數據布局(vEB Layout,不是vEB樹)、Weighted平衡樹、緊湊內存數組(Packed Memory Array or PMA)。其中,vEB是種有趣的嵌套結構,在任意B大小的架構上都不會產生多余的I/O,而PMA則更加神奇,普通靜態數組在空間不足或多余時會double或truncate,這會導致性能在某一次操作的突然下降。而PMA則將普通數組分段,每段中都始終保留一些gap空位,這樣每個segment滿了只會在小范圍內發生數據移動。
推薦閱讀:
CO鼻祖Frigo論文:《Cache-Oblivious Algorithms》
Erik D. Demaine整理的手冊(強烈推薦!):《Cache-Oblivious Algorithms and Data Structures》
論文:《Cache-Efficient Matrix Transposition》
論文(vEB、PMA)(強烈推薦!):《Cache-Oblivious B-Trees》
論文(implict vEB):《Cache Oblivious Search Trees via Binary Trees of Small Height》
論文(PMA):《An Adaptive Packed-Memory Array》
6.基于SSD存儲的算法優化
SSD現在已經不是什么稀罕物了,好多新出的個人筆記本電腦都配備了上百G的SSD,為什么要針對SSD做優化呢?這是因為SSD與傳統磁盤物理結構上的有巨大差別。整塊SSD分為很多個區域,每塊區只能整體擦除。所以為了延遲使用壽命,從OS的I/O調度器和驅動方面和SSD內部的FTL(Flash Translation Layer)方面都會進行均衡。
在這種物理結構之上,SSD有兩個特點:channel-level并行和package-level并行。簡單來說,就是一次I/O的帶寬更大了,以及操作的并行度更高了。所以我們優化數據結構也就有了兩個方向:要么提高一次I/O的數據量,要么提高并行I/O操作數。分析SSD的模型也是在DAM模型上擴展了,額外增加了一個參數P,表示每一步可以并行進行的I/O數。
以B-Tree的搜索操作為例,對于一次操作來說,我們無法預先知道遍歷路徑,所以不能并行。所以我們從第二個方面,提高每次I/O的數據量,從單個塊大小B提升到PB,這樣就充分利用了SSD的特點。對于多個不相關的操作來說,我們可以直接并行請求B-Tree達到同樣的目的。
推薦閱讀:《B+-tree Index Optimization by Exploiting Internal: Parallelism of Flash-based Solid State Drives》
7.算法安全性
7.1 Complexity Attack
復雜度攻擊在前面介紹Hash時提到過,對于普通的數據結構我們可以通過隨機化來避免最壞情況的發生。但對于隨機化算法,如果“敵人”知道了我們的內部信息,例如隨機數產生器的規則,那么也是會出現同樣問題的。例如Skip List變成了階梯形狀,而Treap退化成了鏈表等。
7.2 History-Independent
所謂的History-Independent就是:即便“敵人”得到了目前你數據結構的樣子,它也無法推斷出你調用增刪改查API的操作順序和參數,典型例子有Treap,反例則是Chaining Hash、AVL等等許多數據結構。關于這一部分只是老師上課時不經意提過幾句,理解得還不是太好,感覺很新奇,但沒找到具體專門講解的資料,等找到后要好好學習一下!
?
總結
- 上一篇: ES 在数据量很大的情况下如何提高查询效
- 下一篇: HBase缺陷