內存數據庫中的索引技術

引言

 

傳統的數據庫管理系統把全部數據都放在磁盤上進行管理,因此稱做磁盤數據庫(DRDB: Disk-Resident Database)。磁盤數據庫須要頻繁地訪問磁盤來進行數據的操做,磁盤的讀寫速度遠遠小於CPU處理數據的速度,因此磁盤數據庫的瓶頸出如今磁盤讀寫上。git

 

基於此,內存數據庫的概念被提出來了。內存數據庫(MMDB:Main Memory Database,也叫主存數據庫)[1],就是將數據所有或者大部分放在內存中進行操做的數據庫管理系統,對查詢處理、併發控制與恢復的算法和數據結構進行從新設計,以更有效地使用CPU週期和內存。相對於磁盤,內存的數據讀寫速度要高出幾個數量級,將數據保存在內存中相比從磁盤上訪問可以極大地提升應用的性能。算法

 

近十幾年來,內存的發展一直遵循摩爾定律[2],內存的價格一直降低,而內存的容量一直在增長。如今的主流服務器,幾百GB或者幾TB的內存都很常見,內存的發展使得內存數據庫得以實現。數據庫

 

因爲內存數據庫與傳統的磁盤數據庫在設計和架構上都大不相同,因此傳統的數據庫索引不適用於內存數據庫。研究者爲改進內存數據庫的索引結構作了至關多的研究跟工做。其中,影響較大的索引有早期的T樹、基於緩存敏感(cacheconscious)的CSS/CSB+樹,Trie-tree和Hash等等。本文就這幾種有表明性的索引算法進行研究和分析,爲進一步改進內存數據庫索引算法和提升索引性能打下堅實的基礎。數組

 

二、T-tree

 

2.1 T-tree

 

T-tree是針對主存訪問優化的索引技術[3]。T-tree是一種一個節點中包含多個索引條目的平衡二叉樹,T-tree的索引項不管是從大小仍是算法上都比B-tree精簡得多。T-tree的搜索算法不分搜索的值在當前的節點仍是在內存中的其餘地方,每訪問到一個新的索引節點,索引的範圍減小一半。緩存

 

圖2-1T-Tree的結點服務器

 

T-tree索引用來實現關鍵字的範圍查詢。T-tree是一棵特殊平衡的二叉樹(AVL),它的每一個節點存儲了按鍵值排序的一組關鍵字。T-tree除了較高的節點空間佔有率,遍歷一棵樹的查找算法在複雜程度和執行時間上也佔有優點。如今T-tree己經成爲內存數據庫中最主要的一種索引方式。數據結構

 

T-tree具備如下特色:1)左子樹與右子樹之差不超過1,2)在一個存儲節點能夠保存多個鍵值,它的最左與最右鍵值分別爲這個節點的最小與最大鍵值,它的左子樹僅僅包含那些鍵值小於或等於最小鍵值的一記錄,同理右子樹只包括那些鍵值大於或等於最大鍵值的記錄,3)同時擁有左右子樹的節點被稱爲內部節點,只擁有一個子樹的節點被稱爲半葉節點,沒有子樹的節點被稱爲葉子,4)爲了保持空間的利用率,每個內部節點都須要包含一個最小數目的鍵值。由此可知T-tree是一個每一個結點含有多個關鍵字的平衡二叉樹,每一個節點內的關鍵字有序排列,左子樹都要比根節點關鍵字小,右子樹都要比根節點關鍵字大。架構

 

在上述T-tree結點結構中,包含以下信息:併發

 

(1)balance(平衡因子),其絕對值不大於1,balance=右子樹高度-左子樹高度;dom

 

(2)Left_child_ptr和Right_child_ptr分別表示當前結點的左子樹和右子樹指針;

 

(3)Max_Item表示結點中所能容納的鍵值的最大數;

 

(4)Key[0]至K[Max_Item-1]爲結點內存放的關鍵字;

 

(5)nItem是當前節點實際存儲的關鍵字個數。

 

對於T-tree有以下特徵:

 

(1)與AVL樹類似,T-tree中任何結點的左右子樹的高度之差最大爲1;

 

(2)與AVL樹不一樣,T-tree的結點中可存儲多個鍵值,而且這些鍵值排列有序;

 

(3)T-tree結點的左子樹中容納的鍵值不大於該結點中的最左鍵值;右子樹中容納的鍵值不小於該結點中的最右鍵值;

 

(4)爲了保證每一個結點具備較高的空間佔用率,每一個內部結點所包含的鍵值數目必須不小於某個指定的值,一般爲(Max_Item-2)(Max_Item爲結點中最大鍵值目)。

 

2.2 T-tree索引的操做

 

用T-tree做爲索引方式主要完成三個工做:查找,插入,刪除。其中插入和刪除都是以查找爲基礎。下面分別介紹三種操做的流程。

 

2.2.1 查找

 

T-tree的查找相似於二叉樹,不一樣之處主要在於每一結點上的比較不是針對結點中的各個元素值,而是首先檢查所要查找的目標鍵值是否包含在當前結點的最左鍵值和最右鍵值所肯定的範圍內,若是是的話,則在當前結點的鍵值列表中使用二分法進行查找;若是目標鍵值小於當前結點的最左鍵值,則相似地搜索當前結點的左孩子結點;若是目標鍵值大於當前結點的最右鍵值,則相似地搜索當前結點的右孩子結點。

 

2.2.2 插入

 

T-tree的插入是以查找爲基礎,應用查找操做定位目標鍵值插入位置,並記下查找過程所遇到的最後結點。若是查找成功,判斷此結點中是否有足夠的存儲空間。若是有,則將目標鍵值插入結點中;不然將目標鍵值插入此結點,而後將結點中的最左鍵值插入到它的左子樹中(此時是遞歸插入操做),以後結束;不然分配新結點,並插入目標鍵值;而後根據目標鍵值與結點的最大最小鍵值之間的關係,將新分配的結點連接爲結點的左孩子或右孩子;對樹進行檢查,判斷T-tree的平衡因子是否知足條件,若是平衡因子不知足則執行旋轉操做。

 

2.2.3 刪除

 

T-tree的刪除操做也是以查找爲基礎,應用查找操做定位目標鍵值。若是查找失敗,則結束;不然令N爲目標鍵值所在的結點,並從結點N中刪除目標鍵值;刪除節點後,若是結點N爲空,則刪除結點N,並對樹的平衡因子進行檢查,判斷是否須要執行旋轉操做;若是結點N中的鍵值數量少於最小值,則根據N的平衡因子決定從結點N的左子樹中移出最大的鍵值或者右子樹中移出最小值來填充。

 

2.3 T-tree索引實現關鍵技術

 

實現T-tree索引即要實現T-tree的查找,插入和刪除。其中又以查找爲基礎,對T-tree的維護也就是T-tree的旋轉爲關鍵。當因爲插入或刪除鍵值致使樹的失衡,則要進行T-tree的旋轉。使之從新達到平衡。

 

在插入狀況下,須要依次對全部沿着重新建立結點到根結點路徑中的結點進行檢查,直到出現以下兩種狀況之一時停止:某個被檢查結點的兩個子樹高度相等,此時不須要執行旋轉操做;某個被檢查結點的兩個子樹的高度之差大於1,此時對該結點僅需執行一次旋轉操做便可。

 

在刪除狀況下,相似地須要依次對全部沿着從待刪除結點的父結點到根結點路徑中的結點進行檢查,在檢查過程當中當發現某個結點的左右子樹高度之差越界時,須要執行一次旋轉操做。與插入操做不一樣的是,執行完旋轉操做以後,檢查過程不能停止,而是必須一直執行到檢查完根結點。

 

由此能夠看出,對於插入操做,最多隻須要一次旋轉操做便可使T-tree恢復到平衡狀態;而對於刪除操做則可能會引發向上的連鎖反應,使高層結點發生旋轉,於是可能須要進行屢次旋轉操做。

 

爲了對T-tree進行平衡,須要進行旋轉操做,旋轉是T-tree中最關鍵也是最難的的操做,下面介紹T-tree旋轉的技術。旋轉可分爲四種狀況:由左孩子的左子樹的插入(或者刪除)引發的旋轉記爲LL旋轉,相似有LR,RR及RL旋轉。插入時的狀況與刪除相似。

 

三、CSS/CSB+樹

 

3.1 CSS-trees

 

3.1.1Introduction

 

CSS-trees(Cache-SensitiveSearch Trees),能夠提供比二分查找更爲迅速的查詢操做而又不需大量額外的空間[4]。該技術在在一個以排好序的數組頂端存儲一個目錄結構,且該目錄結構的節點大小與機器cache-line大小相匹配。將該目錄結構存儲在數組中而無需存儲內部節點的指針,子節點可經過數組偏移量定位,這與B+-trees不一樣。

 

3.2 FULL CSS-Tree

 

構造一棵結點包含m個鍵值的查詢樹,樹的深度是d,那麼一直到d-1的深度這棵樹是一棵徹底(m+1)-查詢樹,而在d層葉子結點從左往右分佈。一棵m=4的實例樹圖3-1所示,其中方塊數就是結點數,且每一個結點有四個鍵值。

 

 

CSS-Tree的結點能夠存儲在數組中,如圖3-2所示:

 

 

3.2.1 構造FULL CSS-Tree

 

將一個已排好序的數組構造一棵相應的Full CSS-Tree,首先將數組分爲兩部分,而且在葉子節點和數組元素間創建匹配。而後從最後一個內部節點開始,將節點直接左子樹的最大鍵值做爲節點入口。對於某一些內部節點,也就是最深層最後一個葉子節點的祖先,可能徹底鍵值,能夠用數組前半部分最後的一個元素來填充這些鍵值,因此在某些內部節點會有一些複製的鍵值。儘管要增量更新一棵Full CSS-Tree樹是很困難的,但構造這樣一棵樹花費並不大。實驗代表對於有着兩千五百萬鍵值的數組,構造其相應的Full CSS-Tree花費的時間不足一秒。

 

3.2.2查詢Full CSS-Tree

 

從根節點開始,每次都查詢一個內部節點,利用二分查找來決定查找哪個分支,重複上述行爲直到葉子節點,最後將葉子節點與排好序的數組進行匹配。

 

在節點內全部的查詢都由if-else構成,在內部節點進行二分查找時,一直比較左邊的鍵值是否不小於要查詢的鍵值,當找到第一個比要查詢的鍵值小時,中止比較並進入右邊的分支(若是找不到這樣的值,就進入最左邊的分支)。這樣能夠保證當在節點中有複製的值時,咱們能夠在全部複製的鍵值中找到最左邊的鍵值。

 

3.3 LevelCSS-Tree

 

對於每一個節點有m個記錄的Full CSS-Tree,有嚴格的m個鍵值,全部的記錄都會被利用到。對於m=2t,咱們定義每一個節點只有只有m-1條記錄,而且有一個分支因子m。一棵Level CSS-Tree樹比一棵相應的Full CSS-Tree樹的深度大,由於分支因子是m而不是m+1,而後對於每個節點,須要的同伴數更少。若N爲一個已排好序的數組元素所對應的節點數,Level CSS-Tree有logmN層,而Full CSS-Tree有logm+1N層。每一個節點的同伴數是t,而Full
CSS-tree是t*(1+2/(m+1)),因此Level CSS-tree的總的同伴數是logmN*t=log2N,而Full CSS-tree是logm+1N*t*(1+2/(m+1))=log2N*logm+1m*(1+2(m+1)).所以,Level CSS-Tree所需的companion數比Full CSS-tree少。另外一方面,Level CSS-Tree須要logmN個cache
accesses,遍歷logmN個節點,而Full CSS-Tree需logm+1N。

 

構建一棵Level CSS-Tree與Full CSS-Tree相似,咱們也能夠利用每一個節點的空槽,來存儲最後一個分支的最大值,來避免遍歷整棵子樹來獲取最大元素值。查詢一棵Level CSS-Tree也與查詢Full CSS-Tree相似,惟一的不一樣就是子節點偏移量的計算。

 

3.4 CSB+-Tree

 

3.4.1Introduction

 

儘管CSS-Tree相比二分查找和T-Trees查詢性能更好,可是它是用於決策支持的有着相對靜態數據的工做負載設計的。CSB+-Tree(CacheSensitive B+-Trees)[4],是B+-Trees的變體,連續存儲給定節點的子節點,而且只存儲節點的第一個子節點的地址,其餘子節點的地址能夠經過相對這個子節點的偏移量計算得到。因爲只存儲一個子節點的指針,cache的利用率是很高的,與B+-Tree相似,CSB+-Tree支持增量更新。

 

CSB+-Tree有兩種變體,分段CSB+-Tree(SegmentedCSB+-tree)和徹底CSB+-tree(FullCSB+-Tree).分段CSB+-Tree將子節點分段,在同一段的子節點連續存儲,在每一個節點中,只有每一段的起始地址纔會被存儲。當有分裂時,分段CSB+-Tree能夠減小複製開銷,由於只有一個分段須要移動。徹底CSB+-Tree爲整個節點從新分配空間,所以減小了分裂開銷。

 

3.4.2CSB+-Tree上的操做

 

1)  Bulkload.

 

對於CSB+-Tree樹,一個有效的bulkload方法就是一層一層的創建索引結構。爲每個葉節點分配空間,計算在高層須要的節點數,並給該層分配連續的存儲空間。經過將低層每個節點的最大值填入高層的節點,並設置每個高層節點的第一個子節點指針。重複上述操做直到高層只有一個節點,且這個節點爲根節點。由於同一層的全部節點是連續的,因此構造節點組無需額外的複製操做。

 

2)  Search

 

查詢CSB+-Tree與查詢B+-Tree相似,當最右邊節點的鍵值K比要查詢的鍵值小,給第一個子節點增長K的偏移量來得到子節點的地址。例如,K是節點的第三個鍵值,能夠用一個c語句找到子節點:child=first_child+3,其中child和first_child是節點的指針。

 

3)  Insertion

 

對CSB+-Tree的插入操做也與B+-Tree相似,首先要查找鍵值的插入口,一旦定位至相應葉節點,判斷該葉節點是否有足夠的空間,若是有,就簡單的將鍵值放置在該葉節點中,不然,須要分裂該葉節點。

 

當須要分裂葉節點時,基於父節點是否有足夠的空間存放鍵值會產生兩種狀況。假設父節點p有足夠的空間,令f爲p的第一個子節點的指針,g爲f指向的節點組,構建一個新的比g多了一個節點的節點組g’,將g中全部的節點複製到g’,g中要分裂的節點在g’中變爲兩個節點,更新p中第一個子節點的指針f,使它指向g’,而且從新分配g。

 

當父節點沒有額外的空間而且自身須要分裂時,問題顯得更爲複雜。令f爲p中第一個節點的指針,須要構建新的節點組g’,將g中的節點均分至g’和g中,p中一半的鍵值轉移至g’中。爲了將p分裂爲p和p’,包含p的節點組須要像第一種狀況同樣進行復制,或者,若是節點組也是滿的,咱們須要遞歸的分裂p的父節點。父節點再重複上述操做。

 

4)Deletion

 

刪除操做相似於插入操做,通常的,簡單的定位數據入口而且加以刪除。無需調整樹保證50%的occupancy[5]

 

3.4.3Segmented CSB+-Tree

 

考慮128字節的cache-line,CSB+-Tree中每一個節點最多有30個鍵值,意味着每一個節點能夠有31個子節點,那麼一個節點組最大可達31*128近4KB,所以每個分裂,須要複製4KB的數據來建立一個節點組,若cache-line更大,分裂一個節點的開銷將會更大。

 

修改節點結構能夠減小分裂時的複製操做。能夠將子節點分段,將每一段的地址存儲在節點中,每一段造成了一個節點組,只有在同一段的子節點被連續存儲。第一種考慮是固定每個分段的大小,填充第一個分段的節點,一旦第一個分段滿了,就將節點放在第二個分段。若一個節點落在第二個分段,咱們只需將第二個分段的節點複製到新的段中,而無需管第一個分段,若新的節點落在第一個分段(已經滿了),咱們須要將數據從第一個分段移至第二個分段,在上述例子中,針對隨機插入,分裂產生的數據複製將會減小至1/2(1/2+3/4)*4KB=2.5KB.另外一種就是容許每一個分段的大小不一樣,最終將節點分爲兩段。當有節點插入時,爲這個節點所屬的分段建立一個新的分段,並更新相應分段的大小。在這種方法中,嚴格來講每次插入只涉及到一個分段(但當父節點也須要分裂,此時兩個分段都要複製),若一個新的節點等可能的落入其中一個分段,一個分裂產生的數據複製量爲1/2*4KB=2KB,這種方法能夠進一步的減小數據複製量。有兩個分段的SegmentedCSB+Tree如圖3-3所示(每一個葉節點只有兩個鍵值):

 

 

分段CSB+-Tree可支持全部對樹的操做,方法與非分段CSB+-Tree相似,然而,查找每一個節點的右孩子比起非分段的CSB+-Tree的開銷大,由於須要找到孩子所在的分段。

 

3.4.4FULLCSB+-Tree

 

在FULLCSB+-Tree中,節點分裂的開銷比CSB+-Tree小。在CSB+-Tree中,當節點分裂時,須要將節點組整個複製到新的組中,而在FullCSB+-Tree中,只需訪問節點組的一半。對於這種轉移操做的源地址和目的地址有大的交叉,訪問的cache-line的數目限制在s內。FULLCSB+-Tree在分裂上的平均時間開銷是0.5s,而CSB+-Tree需時2s。

 

3.5 時間空間分析

 

假定鍵值、子節點指針、元組ID有着相同的空間大小K,n爲葉節點數,c爲cache-line的字節數,t爲分段CSB+-Tree的分段數。每一個節點的槽值爲m,其中m=c/K,假定節點大小與cache-line相同,各個參數及其相應的值如圖3-4所示:

 

 

圖3-5顯示了各類方法間分支因子、鍵值差別數、cache未命中數、每一個節點其餘差別的比較。B+-Tree的分支因子比CSS-Tree小,而CSB+-Tree存儲的子節點指針少,所需的分支因子與CSS-Tree相近。這致使每一個方法的cache未命中次數不同。節點的分支因子越大,cache未命中次數相應的越小。在CSB+-Tree每增長一個分段,分支因子就會減小2,這是因爲須要一個槽來存儲子節點指針,另外一個槽來存儲新增分段的大小。通常而言,B+-Tree中節點的70%空間是滿的,須要相應的調整分支因子大小。[6]

 

 

圖 3-5  CSB+-Tree查詢時間分析

 

圖3-6顯示了在分裂時預期要訪問的cache-line數。因爲複製時源地址和目的地址有交叉,因此FullCSB+-Tree所需的數目小。分裂開銷是插入操做總開銷的一部分,另外一部分是定位優葉子節點產生的查詢開銷。分裂開銷相對獨立於樹的深度,這是因爲大多數的分裂都發生在葉節點。然而,當樹的規模愈來愈大時,相應的查詢產生的開銷也會增大。CSB+-Tree的分裂開銷比B+-Tree大,可是插入產生的總開銷還與樹的規模有關。

 

 

圖3-7 顯示了不一樣算法的空間需求。假定全部節點70%的空間是滿的[6],且分別計算內部節點和葉節點的空間大小,假定每一個葉節點有2個兄弟節點指針。內部節點空間大小等於葉節點空間乘以1/(q-1)(q爲分支因子),這裏不比較CSS-Tree,由於CSS-Tree不可能部分滿。

 

 

4    Trie-tree索引

 

4.1 Trie-tree

 

Trie-Tree[7]又稱單詞查找樹或鍵樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計和排序大量的字符串(但不只限於字符串),因此常常被搜索引擎系統用於文本詞頻統計。它的優勢是:最大限度地減小無謂的字符串比較,查詢效率比哈希表高。

 

4.1.1 Trie-tree性質

 

它有三個基本的性質:

 

1)根節點不包含字符,除根節點之外每個節點都只包含一個字符

 

2)從根節點到某一節點,路徑上通過的的字符鏈接起來,爲改節點對應的字符串。

 

3)每一個節點的全部子節點包含的字符都不相同。

 

圖4-1 展現了一個基本的tire-tree結構

 

圖4-1 tire-tree

 

4.1.2 Trie樹的基本實現

 

字母樹的插入、刪除和查找都很是簡單,用一個一重循環便可,即第i次循環找到前i個字母所對應的子樹,而後進行相應的操做。實現這棵字母樹,咱們用最多見的數組保存(靜態開闢內存)便可,固然也能夠開動態的指針類型(動態開闢內存)。至於結點對兒子的指向,通常有三種方法:

 

1)對每一個結點開一個字母集大小的數組,對應的下標是兒子所表示的字母,內容則是這個兒子對應在大數組上的位置,即標號;

 

2)對每一個結點掛一個鏈表,按必定順序記錄每一個兒子是誰;

 

3)使用左兒子右兄弟表示法記錄這棵樹。

 

三種方法,各有特色。第一種易實現,但實際的空間要求較大;第二種,較易實現,空間要求相對較小,但比較費時;第三種,空間要求最小,但相對費時且不易寫。

 

4.1.2.1實現方法

 

搜索字典[8]項目的方法爲:

 

1) 從根結點開始一次搜索;

 

2) 取得要查找關鍵詞的第一個字母,並根據該字母選擇對應的子樹並轉到該子樹繼續進行檢索;

 

3) 在相應的子樹上,取得要查找關鍵詞的第二個字母,並進一步選擇對應的子樹進行檢索。

 

4) 迭代過程……

 

5) 在某個結點處,關鍵詞的全部字母已被取出,則讀取附在該結點上的信息,即完成查找。

 

4.1.2.2 Trie原理

 

Trie的核心思想是空間換時間。利用字符串的公共前綴來下降查詢時間的開銷以達到提升效率的目的。

 

4.1.3 Trie樹的高級實現Double-Array 實現

 

能夠採用雙數組(Double-Array)實現,如圖1.3。利用雙數組能夠大大減少內存使用量,具體實現兩個數組,一個是base[],另外一個是check[]。設數組下標爲i,若是base[i], check[i]均爲0,表示該位置爲空。若是base[i]爲負值,表示該狀態爲終止態(即詞語)。check[i]表示該狀態的前一狀態。

 

定義 1. 對於輸入字符 c,從狀態 s 轉移到狀態 t,雙數組字典樹知足以下條件(圖4-2):

 

 

check[base[s] + c] = s

base[s] + c = t

 

 

 

從定義1中,咱們能獲得查找算法,對於給定的狀態 s 和輸入字符 c :

 

t := base[s] + c;

if check[t] = s then

next state := t

else

fail

endif

 

咱們知道雙數組的實現方法是當狀態有新轉移時才分配空間給新狀態,或能夠表述爲只分配須要轉移的狀態的空間。當遇到沒法知足上述條件時再進行調整,使得其 base 值知足上述條件,這種調整隻影響當前節點下一層節點的重分配,由於全部節點的地址分配是靠 base 數組指定的起始下標所決定的。插入的操做,假設以某字符開頭的 base 值爲i,第二個字符的字符序列碼依次爲c1,
c2, c3…cn,則確定要知足base[i+c1], check[i+c1], base[i+c2], check[i+c2], base[i+c3], check[i+c3]…base[i+cn],check[i+cn]均爲0。

 

圖4-3 Double Array 實現

 

假設,Tire裏有n個節點,字符集大小爲m,則DATrie的空間大小是n+cm,c是依賴於Trie稀疏程度的一個係數。而多路查找樹的空間大小是nm。

 

注意,這裏的複雜度都是按離線算法(offline algorithm)計算的,即處理時已經獲得整個詞庫。在線算法(online algorithm)的空間複雜度還和單詞出現的順序有關,越有序的單詞順序空間佔用越小。

 

查找算法的複雜度和被查找的字符串長度相關的,這個複雜度和多路查找樹是同樣的。

 

插入算法中,若是出現重分配的狀況,咱們要附加上掃描子節點的時間複雜度,還有新base值肯定的算法複雜度。假如這兒咱們都是用暴力算法(for循環掃描),那插入算法時間複雜度是O(nm + cm2)。。

 

實際編碼過程當中,DATrie代碼難度大過多路查找樹,主要是狀態的表示不如樹結構那樣的清晰,下標很容易搞混掉。

 

有個地方須要注意的是,base值正數表示起始偏移量,負數表示該狀態爲終止態,因此在查找新base值時,要保證查到的值是正數。

 

如:空Trie狀態下,插入d時,由於第一個空地址是1,因此獲得base=1-4=-3,這樣base正負的含義就被破壞了。

 

4.1.4 Trie樹的應用

 

Trie是一種很是簡單高效的數據結構,但有大量的應用實例。

 

(1)字符串檢索

 

事先將已知的一些字符串(字典)的有關信息保存到trie樹裏,查找另一些未知字符串是否出現過或者出現頻率。例如:

 

1)給出N個單詞組成的熟詞表,以及一篇全用小寫英文書寫的文章,請你按最先出現的順序寫出全部不在熟詞表中的生詞。

 

2)給出一個詞典,其中的單詞爲不良單詞。單詞均爲小寫字母。再給出一段文本,文本的每一行也由小寫字母構成。判斷文本中是否含有任何不良單詞。例如,若rob是不良單詞,那麼文本problem含有不良單詞。

 

(2)字符串最長公共前綴

 

Trie樹利用多個字符串的公共前綴來節省存儲空間,反之,當咱們把大量字符串存儲到一棵trie樹上時,咱們能夠快速獲得某些字符串的公共前綴。

 

例如:給出N個小寫英文字母串,以及Q個詢問,即詢問某兩個串的最長公共前綴的長度是多少?

 

解決方案:首先對全部的串創建其對應的字母樹。此時發現,對於兩個串的最長公共前綴的長度即它們所在結點的公共祖先個數,因而,問題就轉化爲了離線(Offline)的最近公共祖先(LeastCommon Ancestor,簡稱LCA)問題。

 

而最近公共祖先問題一樣是一個經典問題,能夠用下面幾種方法:

 

1)利用並查集(Disjoint Set),能夠採用採用經典的Tarjan算法;

 

2)求出字母樹的歐拉序列(Euler Sequence)後,就能夠轉爲經典的最小值查詢(Range Minimum Query,簡稱RMQ)問題

 

(3)排序

 

Trie樹是一棵多叉樹,只要先序遍歷整棵樹,輸出相應的字符串即是按字典序排序的結果。例如:給你N個互不相同的僅由一個單詞構成的英文名,讓你將它們按字典序從小到大排序輸出。

 

(4)做爲其餘數據結構和算法的輔助結構,如後綴樹,AC自動機等

 

4.1.5 Trie樹複雜度分析

 

(1)插入、查找的時間複雜度均爲O(N),其中N爲字符串長度。

 

(2)空間複雜度是26^n級別的,很是龐大(可採用雙數組實現改善)。

 

4.1.6總結

 

Trie樹是一種很是重要的數據結構,它在信息檢索,字符串匹配等領域有普遍的應用,同時,它也是不少算法和複雜數據結構的基礎,如後綴樹,AC自動機等。

 

4.2 TrieMemory

 

Trie Memory[9]是一種在內存中存儲和檢索信息的方式,這種方式的優勢是訪問速度快,具備冗餘存儲信息的優勢,主要的缺點是存儲空間利用率很低。

 

4.2.1基本的Trie Memory模型

 

假設咱們須要跟蹤一系列的單詞集合,這些集合是字母組成的序列。這些單詞序列有各類各樣的長度,咱們必須記住的是這些字母組成的有限序列在這個集合中。總得來講,咱們須要判斷一個序列是否是這個集合的成員。

 

剛開始trie僅僅是register組成的一個集合,除此以外還有兩個register,一個是α另外一個是δ,每個register都有cell來存儲整個字母表,若是咱們要存儲「space」的話,每一個register必須擁有27個cell。

 

每個cell都有空間來存儲其它register在內存中的地址,trie中的cell尚未用來存儲信息,一般包含的是register α的地址信息。一個cell若是包含了非register α的register地址,則它表示存儲了信息,這些信息表明了這個cell的名稱,「A」表示A cell,「B」表示B cell。下一個register的地址在序列中。

 

下面用一個例子(圖2.1)來講明,爲了讓例子簡單些,咱們使用字母表的前5個字符來表示總體。而後用▽表示「space」,假設咱們想存儲DAB,BAD,BADE,BE,BED,BEAD,CAB,CAD和A,接下來用圖來講明整個流程。在圖中每一行表明一個register,每一個register有6個cell,最後一行表明第三個特殊的register叫作portal register,是咱們進入系統內存的通道它除了是入口外,也和其它register是同樣的。其它register是編號的。register α將會選擇它們。剛開始的時候register α是register 2。

 

圖4-4 基本Tire Memory模型

 

爲了存儲DAB,咱們引入地址「2」進入portal register的D 單元格,而後咱們移動到register 2而後引入地址「3」到A單元格,而後咱們進入到register 3後把地址「4」放入單元格B,最後咱們移動到register 4 而且把地址「1」放入▽單元,它是終止參數,至此DAB存儲結束。而後咱們轉到第二個單詞BAD,引入地址「5」進入portal register的B單元格來表示字母B,而後到register 5的A單元格寫入地址「6」,再到register 6的D單元格寫入地址「7」,最後到register 7的▽單元格寫入地址「1」。當咱們開始存儲BADE時,咱們發現B,A,D已經在trie中了,所以咱們沿着已經存在的BAD的路徑到register 7而後引入地址「8」到單元格E中去,而後把地址「1」放入register 8的▽單元。

 

4.2.2 Register的類型

 

在剛纔提到的結構中咱們能夠把register分爲4種類型:

 

1)α(address) register來指向下一個存儲信息的地址

 

2)δ(deletion) register

 

3)ν(next) register,下一步將要存儲的信息(在空內存中,它是portal register)

 

4)χ(exterior)類型χ是全部register中尚未接受存儲信息而且沒有被指向爲下一個存儲位置的register。

 

5)ο(occupied)類型ο是存有信息的register

 

4.2.3 Trie的讀和寫

 

在上述的全部的register中除了χ都在trie中,存儲和讀取操做如今可以被簡單的公平的定義以下。

 

4.2.3.1寫操做

 

1)把第i個參數字符傳入下一個register,若是是第一個字符,則是portal
register

 

2)選擇對應字符串的的cell,若是第i個參數字符是字母表的第j個字符,選擇第j個cell。

 

3)檢測來自第i個單元的聯結

 

4)若是這種聯結使得register α:

 

a) 經過αregister把聯結投射到連接的頭部,這樣就能夠存儲信息。

 

b)投射從αregister到連接頭部的聯結來建立一個「next」register(ν)

 

c)最後,把全部的從ν發出來的聯結指向αregister。

 

5)若是源於第j個cell的聯結指向非α register的話,移動到那個register去:

 

a) 若是是第一個register,這參數是一個存儲集合的成員(結束流程)。

 

b)若是不是register 1的話,i加1而且轉到第二步去。

 

4.2.3.2讀操做

 

使用相同的流程,可是不要使用投射,不要投射任何關係,若是聯結指向register 1,則這個參數是存儲集合的一個成員,若是任何點的聯結指向αregister,換句話說這個參數不是存儲集合的成員。

 

5HASH索引

 

HASH就是把關鍵詞直接映射爲存儲地址,達到快速尋址的目的,即Addr=H(key),其中key爲關鍵詞;H爲哈希函數。主要有如下幾種經常使用的哈希函數:

 

1)除留餘數法(DivisionMethod),H(key)=keyMOD p,p通常爲質數;

 

2)隨機數法(RandomMethod),H(key)=random(key),random爲隨機函數;

 

3)平方取中法(MidsquareMethod)。

 

HASH索引結構不須要額外的存儲空間,而且可以在O(1)的時間複雜度下準肯定位到所查找的數據,將磁盤數據庫中的數據查找時間代價優化至最小。Hash索引結構因爲以上優勢在磁盤數據庫中普遍的運用。經歷長久的研究,前後發展出了連接桶哈希(chainedbucket hash)[10],可擴展哈希(extendible hash)[11]、線性哈希(linearhash)[12]和修正的線性哈希(modified linear
hash)[13]。可是這些哈希算法雖然針對內存數據庫進行了少量優化,可是與傳統數據庫中所用的哈希算法沒有明顯不一樣。到了2007年,KennethA. Ross提出了基於現代處理器的Hash預取算法[14]將SIMD指令集融入到Hash算法中,才真正從內存索引的角度改進了哈希算法,提高數據組織的效率。

 

5.1 連接桶哈希

 

連接桶哈希(圖5-1)是一個靜態的結構,可用於內存中與磁盤中。由於它是靜態結構,不用對數據進行重組織,因此它速度很快。但這也是它的缺陷,面對動態數據,就顯得不合適了,由於連接桶哈希必須在使用以前知道哈希表的大小,而這偏偏很難預測。若是預測的表大小太小,其性能會大受影響;若是過大,空間浪費較爲嚴重。最好狀況下,它只有一些空間的浪費,用來存放指向下一個桶的指針。

 

 

5.2 可擴展哈希

 

可擴展哈希(圖5-2)引入了目錄文件的概念,採用可隨數據增加的動態哈希表,所以克服了連接桶哈希的缺陷,其哈希表大小不須要預先知道,一個哈希節點包含多個項,當節點數量溢出時將其分裂爲兩個節點。目錄按2的指數倍增加,當一個節點裝滿並且到達了一個特定的目錄大小目錄就會倍增。哈希函數爲每一個鍵計算一個K位的二進制序列,桶的數量老是使用從序列第一位或者最後一位算起的若干位[]。可是可擴展哈希的一個問題是任意一個節點都會引發目錄的分裂,當哈希函數不夠隨機時,目錄極可能增加的很巨大。

 

 

5.3 線性哈希

 

線性哈希(圖5-3)也使用動態的哈希表,可是同可擴展哈希有較大差異。線性哈希選擇桶數老是使存儲塊的平均記錄保持與容量成一個固定的比例。並且哈希桶不老是能夠分裂,容許有溢出塊。當插入的記錄沒有對應的桶,將其哈希值首位改成0,再次插入,不然直接插入對應桶或其溢出塊中。當記錄數量比容量達到一個閾值,增長一個桶,再分配。相對於可擴展哈希,線性哈希的增加較爲緩慢,重組織的次數和代價都較小。同時,線性散列不須要存放數據桶指針的專門目錄項,且能更天然的處理數據桶已滿的狀況,容許更靈活的選擇桶分裂的時機。

 

 

5.4 修正的線性哈希

 

修正的線性哈希相對於線性哈希主要面向內存環境。經過使用更大的連續節點替代目錄,普通的線性哈希因爲有空節點而浪費空間。並且,除非有一個巧妙的方案解決潛在的虛擬內存映射機制問題,否則每次目錄增加時那個連續的節點都要被拷貝到一個更大的內存塊。修正的線性哈希採用跟可擴展哈希同樣的目錄,除了目錄爲線性增加的,連接的是單個項目的節點和分配內存是從一個常規的內存池。這個算法節點分裂的準則是基於性能,舉例來講,監控哈希鏈的平均長度比監控存儲利用率可以更直接的控制平均搜索和更新時間[13]。

 

5.5 Hash預取算法

 

Hash預取算法面向的是鍵和哈希值都是32位的場景,特意對內存環境進行了優化。此算法使用乘法散列,這種方法十分廣泛、計算高效,更重要的是適用於矢量,達到了一次計算多個哈希函數的目的[14]。針對現代處理器的SIMD架構,將鍵值與哈希值共同放在一個指令當中,達到大大減小指令數的目的,令每次所需的數據長度剛好等於L2的cacheline,大大下降了性能代價,在內存環境中,大大提升了cache的性能。

 

參考文獻

 

[1] Garcia-Molina H, Salem K. Main memorydatabase systems: An overview[J]. Knowledge and Data Engineering, IEEETransactions on, 1992, 4(6): 509-516.

 

[2] Moore G E. Cramming more components ontointegrated circuits[J]. 1965.

 

[3] Lehman T J, Carey M J. A study of indexstructures for main memory database management systems[C]//Conference on VeryLarge Data Bases. 1986, 294.

 

[4] Jun Rao,Kenneth A. Ross:CacheConscious Indexing for Decision-Support in Main Memory,VLDB 1999: 78-89

 

[5]RaghuRamakrishnan. Database Management Systems. McGraw-Hill, 1997.

 

[6] AndrewYao. On random 2-3 trees. Acta Informatica, 9:159{170, 1978.

 

[7]Black,Paul E. (2009-11-16). 「trie」. Dictionary of Algorithms and Data Structures. NationalInstitute of Standards and Technology. Archived from the original on2010-05-19.

 

[8] Knuth,Donald(1997). 「6.3: Digital Searching」.The Art of ComputerProgramming Volume 3: Sorting and Searching.Addison-Wesley.p.492.

 

[9] FREDKIN E.Tire Memory[J]. Communication of theACM,1960,3(9):490-499.

 

[10] Knuth D. The Art of ComputerProgramming 1: Fundamental Algorithms 2: Seminumerical Algorithms 3: Sortingand Searching[J]. 1968.

 

[11] Fagin R, Nievergelt J, Pippenger N, et al.Extendible hashing—a fast access method for dynamic files[J]. ACM Transactionson Database Systems (TODS), 1979, 4(3): 315-344.

 

[12] Litwin W. Linear Hashing: a new tool forfile and table addressing[C]//VLDB. 1980, 80: 1-3.

 

[13] Lehman T J, Carey M J. A study of indexstructures for main memory database management systems[C]//Conference on VeryLarge Data Bases. 1986, 294.

 

[14] Ross K A. Efficient Hash Probes on ModernProcessors[C]//ICDE. 2007: 1297-1301.

相關文章
相關標籤/搜索