310 likes | 643 Views
資料結構設計與 C++ 程式應用 Fundamentals of Data Structures and Their Applications Using C++. 第 10 章 赫序函數. 資料結構設計與 C++ 程式應用 版權所有 禁止重製. 何謂赫序函數 (Hashing Function ). 赫序函數 (Hashing Function):
E N D
資料結構設計與C++程式應用 Fundamentals of Data Structures and Their Applications Using C++ 第10章 赫序函數 資料結構設計與C++程式應用 版權所有 禁止重製
何謂赫序函數(Hashing Function ) • 赫序函數(Hashing Function): 透過一個數學函數來計算(或轉換)一個鍵值所對應的位址 • 碰撞(Collision): 當一個位址同時被兩個或兩個以上的鍵值所對應時,我們稱此現象為碰撞(Collision)
傳統的赫序搜尋法 • 除法(Division Method) h(k)=(k mod m)+1 k:代表資料的鍵值。 m:代表位址空間的大小。 根據經驗,m為偶數時較易發生碰撞。因此,專家們建議要嘛乾脆取一個質數當做m;不然的話,m可以找一個奇數且沒有任何一個質因數小於20者為之。 【例】 除法 鍵值集合 k ={34,59,72,95}。令m=7,則利用 h(k)=(k mod 7)+1,可以將鍵值34,59,72,95分別存放到第7,第4,第3及第5號位址上。
傳統的赫序搜尋法 • 平方取中法(Midsquare Method) 鍵值:k 鍵值平方:k2 令 m=99…9代表位址空間的大小,且 m 為 t 位數。則取 k2的中間t位數之值當作鍵值 k 所對應的位址。 【例】 平方取中法 設鍵值 k=113586。令m=9999,故 m 為4位數正整數。則 h(k)=k2取中間4位數 =12901779396取中間4位數 =1779。
傳統的赫序搜尋法 • 疊合法(Folding Method) 將鍵值 k 切割成若干長度相等的區塊,將之相加,再 mod m(意即除以m取餘數)而得,其中 m 為位址空間的大小。 【例】 疊合法 設鍵值 k=187249653,且 m=1000。第一區塊 第二區塊 第三區塊 則首先將 k 切成三個區塊:187,249,653 187 + 249 + 653 = 1089。 h(k) = 1089 mod 1000 = 89。原第一區塊的翻轉原第三區塊的翻轉 也有人建議為了更減少碰撞之機率,有時亦可考慮將所切割的各區塊,在相加之前先將奇數號的區塊翻轉過來,再做加法。如上例: 781 249 +356 1386 h(k) = 1386 mod 1000 = 386。
傳統的赫序搜尋法 • 基數法(Radix Method) 將鍵值視為某一基數數系中的數值,再將該數值轉換回原鍵值所在的基數數系,接著,取出該數的某些位數值當做赫序函數的對應值。 鍵值:(k)p,其中 p 為一個基數。 ↓看成 (k)q,其中 q>p,且 q 與 p 互質。 ↓從 k 中取出一部份 k' (k')p當做 h(k)之值 【例】 基數法 鍵值 k=(530476)10,在此數中,P=10。首先將k=(530476)10看成(530476)11,此時 q=11,且q與p互質。再將(530476)11轉換回原 k 所在的基數數系;亦即,(530476)11=5*115 + 3*114 + 4*112 + 7*111+6*110 = (849745)10。接著取後三位數當作赫序函數值;亦即 h(k) = 745。
傳統的赫序搜尋法 • 代數碼法(The Algebraic Coding Method) 【例】 代數碼法 k = (1615)10 = (011001001011)2 =(a1,a2,...,a12)2。因此,r=12。 造一個多項式:K(x)=x+x2+x5+x8+x10+x11。 令位址空間大小:m=210=1024,故t=10。 造一個多項式:p(x)=x10+x8+x5+x4+x2+x+1。 計算 K(x) mod p(x)=x9+x7+x5+x4+x3。 故 h(1615)=(1010111000)2 = 696。
傳統的赫序搜尋法 • 乘法(Multiplication Hashing) 鍵值:k 常數:c 且 0<c<1 位址空間大小:m h(k):+1。 請注意:ck mod 1代表 ck 這個數的小數部份之值。 • 位數分析法(Digit Analysis) 位數分析法主要用於十進位制的各個鍵值之位數比較,採用分佈較均勻的若干個位數值做為每一個鍵值的赫序函數值。
解決碰撞的方法 • 循序定址法(Linear Probing/ Linear Open Addressing) 原理上,當 h(ki)=α=h(kj)時,表示 kj 與 ki撞在一起了。這時候,我們可以沿著位址 α 的下面,逐號地尋找一個空的位置來讓 kj棲身。如果一路找到位址空間的最後一號位置都沒有找到空位置的話,則可以再折回位址空間的第一號位置,再從上往下去搜尋,直到找到 α 的前一號位置為止。
解決碰撞的方法 • 多次定址法(Quadratic Probing) 當有碰撞產生時,利用較不規則的跳躍去尋找下一個可能空的位置。 當鍵值 k 的赫序函數值 h(k) 和其它的鍵值撞在一起時,可以第一次考慮試 (h(k)+12) mod m之位址看看該位置是否有空;否則的話,第二次可以試 (h(k)+22) mod m之位址看看,依此類推。
解決碰撞的方法 • 亂數法(Random Method) 當產生碰撞時,則將該鍵值以亂數赫序函數再重新計算一個新址。如果用數學描述法來表示,亂數法可以寫成: h(k)+s(i),1≦ i ≦m-1。 s(i)為一個虛擬亂數產生程式,i為該亂數產生程式的種子(Seed)。例如 i← (i+c) mod m,假設 i 為亂數產生程式的種子(亦即起始值),而 c 與 m 互質。令 c=7,m=11,且一開始的時候,i=3,則產生的亂數為 10,6,2,9,5,1,8,4,0,7,3。
解決碰撞的方法 • 重複赫序法(Rehashing) 當有碰撞現象產生時,再改用事先準備好的其它種赫序函數計算出不同的位址,若再碰撞,則再找出另一個赫序函數來計算位址,依此類推。因此,重複赫序法需要事先準備一些赫序函數 h1,h2,…,hr,以備不時之需。 • 開放定址法(Open Addressing),又稱掛籃法(Bucketing) 讓每一個位置的空間加大,就像一個籃子一般,讓它可以存放不只一個鍵值,使之不致於因為稍一碰撞,就非得四處尋找蔽護不可。然而,縱使一個籃子可以多放幾個鍵值,畢竟它也有滿溢的時候,當籃子滿溢時究竟該怎麼辦呢?做法很簡單,可以再造一個籃子,讓新的籃子和舊的籃子邏輯上相連在一起。
解決碰撞的方法 • 串列法(List Method) 利用一塊自由區 (Free Storage Area) 做為碰撞之鍵值棲身之難民營,當有碰撞產生時,就將這個後來的鍵值放到自由區的空位置裡,而利用指標與原住民 (即較早赫序到同一位址之鍵值) 相連。
赫序函數之建議與評估 一個較合用的赫序函數需考慮下列幾個因素 • 靜態資料或動態資料 • 定址所需時間 • 用硬體來發展赫序函數 • 鍵值長度 • 鍵值基數 • 位址空間的大小 • 資料存取的頻率 • 對於不成功的存取(Unsuccessful Search),能否很快地確定所要尋找的鍵值根本不在檔案裡?
赫序函數之建議與評估 赫序函數好壞的評估原則: • 存取資料的速度快嗎? • 資料的新增是否方便? • 刪除資料是否方便? • 記憶體是否佔用很多? • 有沒有充份利用到鍵值的大小? • 有沒有利用到鍵值的存取頻率?
動態完美赫序函數 動態赫序函數大致上可以分成二類: • 需要索引輔助區域的動態赫序函數 : Litwin的虛擬赫序(Virtual Hashing)、 Larson 動態赫序(Dynamic Hashing)、 Fagin等人的延展赫序(Extendible Hashing) 。 2. 不須要索引輔助的動態赫序函數: Litwin的Trie Hashing 。
動態完美赫序函數 • Larson的動態赫序函數 利用了串列樹(Link-Tree)的架構,建構了一個稱之為索引的輔助區域,藉以偵測當檔案有新記錄加入時,其所指定儲存的區段當時是否已經放滿了記錄?若是,則需將該區段一分為二。至於這些須要儲存的記錄,應被放置於那一個區段?是由我們所謂的動態赫序函數對應於該記錄的主鍵值所決定的。
動態完美赫序函數 • Larson的動態赫序函數 【例】 H0為一赫序函數,用來決定想要儲於檔案中的記錄類別,進而利用該類別決定儲存記錄的區段。
動態完美赫序函數 • Larson的動態赫序函數 假設有一筆新的記錄其主鍵值為 “in”,欲加入檔案中,而且H0(“in”)=1,但因每個區段最多只能容納四筆記錄,故區段0便發生了碰撞的現象,因此必須對區段0做一次分裂(Split)的程序處理。 因此可到以下結果:
動態完美赫序函數 • Larson的動態赫序函數 索引呈現一個樹林(Forest)的結構,而每棵樹都是二元樹(Binary Tree)。在這些樹的節點中屬於外部節點者(亦即所謂的樹葉(Leaf)都會指到一個區段的位址上;因此,當使用者想找尋某一筆記錄時,首先利用 H0函數來分辨其在索引中所屬的二元樹。決定完所屬的二元樹之後,再檢查其是否為外部節點(External Node)?
動態完美赫序函數 • Litwin的虛擬赫序函數 假設每一個區段最多可以存放四筆記錄。 以 N=100 為例,將記錄分散在編號0至99的區段中,而如圖(a),當新加入一筆主鍵值為4900的記錄時,造成區段0發生了碰撞的現象,因而對應於區段0之赫序函數的j值必須加1,故造成了區段0中的五筆記錄自然地分散到區段0和區段100,如圖(b)。
動態完美赫序函數 • 延展赫序函數 保證在尋找(Search)記錄的過程中絕對不曾超過兩次的磁碟機讀取次數(Disk Access Time)。 【例】
動態完美赫序函數 • 延展赫序函數 若有一筆新的記錄其主鍵值為“in”且 H(“in”)=01…,則因區段0現已經額滿了,所以該區段會一分為二,將原來區段0中的記錄分散在區段0和區段3。分裂後的結果 如下:
動態完美赫序函數 • 延展赫序函數 假如因某一筆新記錄的加入而造成對於某一個區段的碰撞,則必須做分裂的動作,而且由於這一個分裂的動作使得索引中的d值加1,則整個索引的節點數將會爆增一倍。
動態完美赫序函數 • Trie赫序函數 儲存記錄時須依據主鍵值的大小次序來儲存 。 Trie的資料結構 :
動態完美赫序函數 • Trie赫序函數 【例】 檔案開始時空無一物,如圖(a)所示。假設目前有四筆記錄儲存進來,則區段的內容如圖(b)所示。
動態完美赫序函數 • Trie赫序函數 此時若有一筆新的記錄且其主鍵值為 "a" 欲加入此檔案,便發生了碰撞的現象,我們必須將區段 0 做分裂處理。
動態完美赫序函數 • Trie赫序函數 將其隱含的意義以更明確的類似二元樹的架構表示出來
動態完美赫序函數 • Trie赫序函數 當加入的新記錄到達一定的程度之後,區段 0 或區段 1 可能再度發生碰撞
動態完美赫序函數 • Trie赫序函數 圖(a) 中,Trie 0 節點的 LP=-1,此乃因為原來 Trie 0 的 LP 的區段發生了碰撞分裂,而該處理程序會建造出一個新的區段和 Trie 節點。圖(b)將此五筆記錄重新依 DN=0,DV='i' 的規則分配到區段 0 和區段 2。
動態完美赫序函數 • Trie赫序函數 Trie 的另一種表示法