* 若結構中存在關鍵字和K相等的記錄,則必定存儲在f(K)的位置上。由此,不需比較便可直接取得所查記錄。這個對應關係f稱爲散列函數(Hash function),按這個思想建立的表爲散列表。
* 對不同的關鍵字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),這種現象稱衝突。具有相同函數值的關鍵字對該散列函數來說稱做同義詞。
根據散列函數H(key)和處理衝突的方法將一組關鍵字映象到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的“象”, 作爲這條記錄在表中的存儲位置,這種表便稱爲散列表,這一映象過程稱爲散列造表或散列,所得的存儲位置稱散列地址。這個現象也叫散列桶,在散列桶中,只能通過順序的方式來查找,一般只需要查找三次就可以找到。科學家計算過,當負載因子(load factor)不超過75%,查找效率最高。
散列表(Hash table,也叫哈希表):是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過映射的位置(即把關鍵碼值映射到表中一個位置)來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
以下是別人寫的文章,加深對散列表的理解:
哈希表是種數據結構,它可以提供快速的插入操作和查找操作。第一次接觸哈希表時,它的優點多得讓人難以置信。不論哈希表中有多少數據,插入和刪除(有時包括側除)只需要接近常量的時間即0(1)的時間級。實際上,這只需要幾條機器指令。
對哈希表的使用者一一人來說,這是一瞬間的事。哈希表運算得非常快,在計算機程序中,如果需要在一秒種內查找上千條記錄通常使用哈希表(例如拼寫檢查器)哈希表的速度明顯比樹快,樹的操作通常需要O(N)的時間級。哈希表不僅速度快,編程實現也相對容易。
哈希表也有一些缺點它是基於數組的,數組創建後難於擴展某些哈希表被基本填滿時,性能下降得非常嚴重,所以程序雖必須要清楚表中將要存儲多少數據(或者準備好定期地把數據轉移到更大的哈希表中,這是個費時的過程)。
而且,也沒有一種簡便的方法可以以任何一種順序〔例如從小到大〕遍歷表中數據項。如果需要這種能力,就只能選擇其他數據結構。
然而如果不需要有序遍歷數據,井且可以提前預測數據量的大小。那麼哈希表在速度和易用性方面是無與倫比的。
哈希表算法-哈希表的概念及作用
一般的線性表,樹中,記錄在結構中的相對位置是隨機的,即和記錄的關鍵字之間不存在確定的關係,因此,在結構中查找記錄時需進行一系列和關鍵字的比較。這一類查找方法建立在“比較“的基礎上,查找的效率依賴於查找過程中所進行的比較次數。
理想的情況是能直接找到需要的記錄,因此必須在記錄的存儲位置和它的關鍵字之間建立一個確定的對應關係f,使每個關鍵字和結構中一個唯一的存儲位置相對應。
哈希表最常見的例子是以學生學號爲關鍵字的成績表,1號學生的記錄位置在第一條,10號學生的記錄位置在第10條...
如果我們以學生姓名爲關鍵字,如何建立查找表,使得根據姓名可以直接找到相應記錄呢?
用上述得到的數值作爲對應記錄在表中的位置,得到下表:
上面這張表即哈希表。
如果將來要查李秋梅的成績,可以用上述方法求出該記錄所在位置:
李秋梅:lqm 12+17+13=42 取表中第42條記錄即可。
問題:如果兩個同學分別叫 劉麗 劉蘭 該如何處理這兩條記錄?
這個問題是哈希表不可避免的,即衝突現象:對不同的關鍵字可能得到同一哈希地址。
哈希表算法-哈希表的構造方法
1、直接定址法
例如:有一個從1到100歲的人口數字統計表,其中,年齡作爲關鍵字,哈希函數取關鍵字自身。
但這種方法效率不高,時間複雜度是O(1),空間複雜度是O(n),n是關鍵字的個數
2、數字分析法
有學生的生日數據如下:
年.月.日
75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15
...
經分析,第一位,第二位,第三位重複的可能性大,取這三位造成衝突的機會增加,所以儘量不取前三位,取後三位比較好。
3、平方取中法
取關鍵字平方後的中間幾位爲哈希地址。
4、摺疊法
將關鍵字分割成位數相同的幾部分(最後一部分的位數可以不同),然後取這幾部分的疊加和(捨去進位)作爲哈希地址,這方法稱爲摺疊法。
例如:每一種西文圖書都有一個國際標準圖書編號,它是一個10位的十進制數字,若要以它作關鍵字建立一個哈希表,當館藏書種類不到10,000時,可採用此法構造一個四位數的哈希函數。如果一本書的編號爲0-442-20586-4,則:
5、除留餘數法
取關鍵字被某個不大於哈希表表長m的數p除後所得餘數爲哈希地址。
H(key)=key MOD p (p<=m)
6、隨機數法
選擇一個隨機函數,取關鍵字的隨機函數值爲它的哈希地址,即
H(key)=random(key) ,其中random爲隨機函數。通常用於關鍵字長度不等時採用此法。
5、除留餘數法
取關鍵字被某個不大於哈希表表長m的數p除後所得餘數爲哈希地址。
H(key)=key MOD p (p<=m)
6、隨機數法
選擇一個隨機函數,取關鍵字的隨機函數值爲它的哈希地址,即
H(key)=random(key) ,其中random爲隨機函數。通常用於關鍵字長度不等時採用此法。
5、除留餘數法
取關鍵字被某個不大於哈希表表長m的數p除後所得餘數爲哈希地址。
H(key)=key MOD p (p<=m)
6、隨機數法
選擇一個隨機函數,取關鍵字的隨機函數值爲它的哈希地址,即
H(key)=random(key) ,其中random爲隨機函數。通常用於關鍵字長度不等時採用此法。
哈希表算法-處理衝突的方法
如果兩個同學分別叫 劉麗 劉蘭,當加入劉蘭時,地址24發生了衝突,我們可以以某種規律使用其它的存儲位置,如果選擇的一個其它位置仍有衝突,則再選下一個,直到找到沒有衝突的位置。選擇其它位置的方法有:
1、開放定址法
Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)
其中m爲表長,di爲增量序列
如果di值可能爲1,2,3,...m-1,稱線性探測再散列。
如果di取值可能爲1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2)
稱二次探測再散列。
如果di取值可能爲僞隨機數列。稱僞隨機探測再散列。
例:在長度爲11的哈希表中已填有關鍵字分別爲17,60,29的記錄,現有第四個記錄,其關鍵字爲38,由哈希函數得到地址爲5,若用線性探測再散列,如下:
2、再哈希法
當發生衝突時,使用第二個、第三個、哈希函數計算地址,直到無衝突時。缺點:計算時間增加。
3、鏈地址法
將所有關鍵字爲同義詞的記錄存儲在同一線性鏈表中。
4、建立一個公共溢出區
假設哈希函數的值域爲[0,m-1],則設向量HashTable[0..m-1]爲基本表,另外設立存儲空間向量OverTable[0..v]用以存儲發生衝突的記錄。
轉載地址: http://www.cnblogs.com/jiewei915/archive/2010/08/09/1796042.html
C#中實現了哈希表數據結構的集合類有:
(1) System.Collections.Hashtable
(2) System.Collections.Generic.Dictionary
前者爲一般類型的哈希表,後者是泛型版本的哈希表。Dictionary和Hashtable之間並非只是簡單的泛型和非泛型的區別,兩者使用了完全不同的哈希衝突解決辦法。
Hashtable 和 Dictionary <K, V> 類型 的區別
1:單線程程序中推薦使用 Dictionary, 有泛型優勢, 且讀取速度較快, 容量利用更充分.
2:多線程程序中推薦使用 Hashtable, 默認的 Hashtable 允許單線程寫入, 多線程讀取, 對 Hashtable 進一步調用 Synchronized() 方法可以獲得完全線程安全的類型. 而 Dictionary 非線程安全, 必須人爲使用 lock 語句進行保護, 效率大減.
3:Dictionary 有按插入順序排列數據的特性 (注: 但當調用 Remove() 刪除過節點後順序被打亂), 因此在需要體現順序的情境中使用 Dictionary 能獲得一定方便.
4:對於值類型,特定類型(不包括 Object)的 Dictionary<(Of <(TKey, TValue>)>) 的性能優於 Hashtable,這是因爲 Hashtable 的元素屬於 Object 類型,所以在存儲或檢索值類型時通常發生裝箱和取消裝箱操作。
哈希衝突解決方法
哈希函數的目標是儘量減少衝突,但實際應用中衝突是無法避免的,所以在衝突發生時,必須有相應的解決方案。而發生衝突的可能性又跟以下兩個因素有關:
(1) 裝填因子α:所謂裝填因子是指合希表中已存入的記錄數n與哈希地址空間大小m的比值,即 α=n / m ,α越小,衝突發生的可能性就越小;α越大(最大可取1),衝突發生的可能性就越大。這很容易理解,因爲α越小,哈希表中空閒單元的比例就越大,所以待插入記錄同已插入的記錄發生衝突的可能性就越小;反之,α越大,哈希表中空閒單元的比例就越小,所以待插入記錄同已插入記錄衝突的可能性就越大;另一方面,α越小,存儲窨的利用率就越低;反之,存儲窨的利用率就越高。爲了既兼顧減少衝突的發生,又兼顧提高存儲空間的利用率,通常把α控制在0.6~0.9的範圍之內,C#的HashTable類把α的最大值定爲0.72。
(2) 與所採用的哈希函數有關。若哈希函數選擇得當,就可使哈希地址儘可能均勻地分佈在哈希地址空間上,從而減少衝突的發生;否則,就可能使哈希地址集中於某些區域,從而加大沖突發生的可能性。
衝突解決技術可分爲兩大類:開散列法(又稱爲鏈地址法)和閉散列法(又稱爲開放地址法)。哈希表是用數組實現的一片連續的地址空間,兩種衝突解決技術的區別在於發生衝突的元素是存儲在這片數組的空間之外還是空間之內:
(1) 開散列法發生衝突的元素存儲於數組空間之外。可以把“開”字理解爲需要另外“開闢”空間存儲發生衝突的元素。
(2) 閉散列法發生衝突的元素存儲於數組空間之內。可以把“閉”字理解爲所有元素,不管是否有衝突,都“關閉”於數組之中。閉散列法又稱開放地址法,意指數組空間對所有元素,不管是否衝突都是開放的。
閉散列法(開放地址法)
閉散列法是把所有的元素存儲在哈希表數組中。當發生衝突時,在衝突位置的附近尋找可存放記錄的空單元。尋找“下一個”空位的過程稱爲探測。上述方法可用如下公式表示:
hi=(h(key)+di)%m i=1,2,…,k (k≤m-1)
其中h(key)爲哈希函數;m爲哈希表長;di爲增量的序列。根據di取值的不同,可以分成幾種探測方法,下面只介紹Hashtable所使用到的雙重散列法。
雙重散列法
雙重散列法又稱二度哈希,是閉散列法中較好的一種方法,它是以關鍵字的另一個散列函數值作爲增量。設兩個哈希函數爲:h1和h2,則得到的探測序列爲:
(h1(key)+h2(key))%m,(h1(key)+2h2(key))%m,(h1(key)+3h2(key))%m,…
其中,m爲哈希表長。由此可知,雙重散列法探測下一個開放地址的公式爲:
(h1(key) + i * h2(key)) % m (1≤i≤m-1)
定義h2的方法較多,但無採用什麼方法都必須使h2(key)的值和m互素(又稱互質,表示兩數的最大公約數爲1,或者說是兩數沒有共同的因子,1除外)才能使發生衝突的同義詞地址均勻地分佈在整個哈希表中,否則可能造成同義詞地址的循環計算。若m爲素數,則h2取1至m-1之間的任何數均與m互素,因此可以簡單地將h2定義爲:
h2(key) = key % (m - 2) + 1
剖析System.Collections.Hashtable
萬物之母object類中定義了一個GetHashCode()方法,這個方法默認的實現是返回一個唯一的整數值以保證在object的生命期中不被修改。既然每種類型都是直接或間接從object派生的,因此所有對象都可以訪問該方法。自然,字符串或其他類型都能以唯一的數字值來表示。也就是說,GetHashCode()方法使得所有對象的哈希函數構造方法都趨於統一。當然,由於GetHashCode()方法是一個虛方法,你也可以通過重寫這個方法來構造自己的哈希函數。
Hashtable的實現原理
Hashtable使用了閉散列法來解決衝突,它通過一個結構體bucket來表示哈希表中的單個元素,這個結構體中有三個成員:
(1) key :表示鍵,即哈希表中的關鍵字。
(2) val :表示值,即跟關鍵字所對應值。
(3) hash_coll :它是一個int類型,用於表示鍵所對應的哈希碼。
int類型佔據32個位的存儲空間,它的最高位是符號位,爲“0”時,表示這是一個正整數;爲“1”時表示負整數。hash_coll使用最高位表示當前位置是否發生衝突,爲“0”時,也就是爲正數時,表示未發生衝突;爲“1”時,表示當前位置存在衝突。之所以專門使用一個位用於存放哈希碼並標註是否發生衝突,主要是爲了提高哈希表的運行效率。關於這一點,稍後會提到。
Hashtable解決衝突使用了雙重散列法,但又跟前面所講的雙重散列法稍有不同。它探測地址的方法如下:
h(key, i) = h1(key) + i * h2(key)
其中哈希函數h1和h2的公式如下:
h1(key) = key.GetHashCode()
h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1))
由於使用了二度哈希,最終的h(key, i)的值有可能會大於hashsize,所以需要對h(key, i)進行模運算,最終計算的哈希地址爲:
哈希地址 = h(key, i) % hashsize
【注意】:bucket結構體的hash_coll字段所存儲的是h(key, i)的值而不是哈希地址。
哈希表的所有元素存放於一個名稱爲buckets(又稱爲數據桶) 的bucket數組之中,下面演示一個哈希表的數據的插入和刪除過程,其中數據元素使用(鍵,值,哈希碼)來表示。注意,本例假設Hashtable的長度爲11,即hashsize = 11,這裏只顯示其中的前5個元素。
(1) 插入元素(k1,v1,1)和(k2,v2,2)。
由於插入的兩個元素不存在衝突,所以直接使用h1(key) % hashsize的值做爲其哈希碼而忽略了h2(key)。其效果如圖8.6所示。
(2) 插入元素(k3,v3,12)
新插入的元素的哈希碼爲12,由於哈希表長爲11,12 % 11 = 1,所以新元素應該插入到索引1處,但由於索引1處已經被k1佔據,所以需要使用h2(key)重新計算哈希碼。
h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1))
h2(key) = 1 + ((12 >> 5) + 1) % (11 - 1)) = 2
新的哈希地址爲 h1(key) + i * h2(key) = 1 + 1 * 2 = 3,所以k3插入到索引3處。而由於索引1處存在衝突,所以需要置其最高位爲“1”。
(10000000000000000000000000000001)2 = (-2147483647)10
最終效果如圖8.7所示。
(3) 插入元素(k4,v4,14)
k4的哈希碼爲14,14 % 11 = 3,而索引3處已被k3佔據,所以使用二度哈希重新計算地址,得到新地址爲14。索引3處存在衝突,所以需要置高位爲“1”。
(12)10 = (00000000000000000000000000001100)2 高位置“1”後
(10000000000000000000000000001100)2 = (-2147483636)10
最終效果如圖8.8所示。
(4) 刪除元素k1和k2
Hashtable在刪除一個存在衝突的元素時(hash_coll爲負數),會把這個元素的key指向數組buckets,同時將該元素的hash_coll的低31位全部置“0”而保留最高位,由於原hash_coll爲負數,所以最高位爲“1”。
(10000000000000000000000000000000)2 = (-2147483648)10
單憑判斷hash_coll的值是否爲-2147483648無法判斷某個索引處是否爲空,因爲當索引0處存在衝突時,它的hash_coll的值同樣也爲-2147483648,這也是爲什麼要把key指向buckets的原因。這裏把key指向buckets並且hash_coll值爲-2147483648的空位稱爲“有衝突空位”。如圖8.8所示,當k1被刪除後,索引1處的空位就是有衝突空位。
Hashtable在刪除一個不存在衝突的元素時(hash_coll爲正數),會把鍵和值都設爲null,hash_coll的值設爲0。這種沒有衝突的空位稱爲“無衝突空位”,如圖8.9所示,k2被刪除後索引2處就屬於無衝突空位,當一個Hashtable被初始化後,buckets數組中的所有位置都是無衝突空位。
哈希表通過關鍵字查找元素時,首先計算出鍵的哈希地址,然後通過這個哈希地址直接訪問數組的相應位置並對比兩個鍵值,如果相同,則查找成功並返回;如果不同,則根據hash_coll的值來決定下一步操作。當hash_coll爲0或正數時,表明沒有衝突,此時查找失敗;如果hash_coll爲負數時,表明存在衝突,此時需通過二度哈希繼續計算哈希地址進行查找,如此反覆直到找到相應的鍵值表明查找成功,如果在查找過程中遇到hash_coll爲正數或計算二度哈希的次數等於哈希表長度則查找失敗。由此可知,將hash_coll的高位設爲衝突位主要是爲了提高查找速度,避免無意義地多次計算二度哈希的情況。
Hashtable的代碼實現
哈希表的實現較爲複雜,爲了簡化代碼,本例忽略了部分出錯判斷,在測試時請不要設key值爲空
using System; 2 public class Hashtable 3 { 4 private struct bucket 5 { 6 public Object key; //鍵 7 public Object val; //值 8 public int hash_coll; //哈希碼 9 } 10 private bucket[] buckets; //存儲哈希表數據的數組(數據桶) 11 private int count; //元素個數 12 private int loadsize; //當前允許存儲的元素個數 13 private float loadFactor; //填充因子 14 //默認構造方法 15 public Hashtable() : this(0, 1.0f) { } 16 //指定容量的構造方法 17 public Hashtable(int capacity, float loadFactor) 18 { 19 if (!(loadFactor >= 0.1f && loadFactor <= 1.0f)) 20 throw new ArgumentOutOfRangeException( 21 "填充因子必須在0.1~1之間"); 22 this.loadFactor = loadFactor > 0.72f ? 0.72f : loadFactor; 23 //根據容量計算表長 24 double rawsize = capacity / this.loadFactor; 25 int hashsize = (rawsize > 11) ? //表長爲大於11的素數 26 HashHelpers.GetPrime((int)rawsize) : 11; 27 buckets = new bucket[hashsize]; //初始化容器 28 loadsize = (int)(this.loadFactor * hashsize); 29 } 30 public virtual void Add(Object key, Object value) //添加 31 { 32 Insert(key, value, true); 33 } 34 //哈希碼初始化 35 private uint InitHash(Object key,int hashsize, 36 out uint seed,out uint incr) 37 { 38 uint hashcode = (uint)GetHash(key) & 0x7FFFFFFF; //取絕對值 39 seed = (uint)hashcode; //h1 40 incr = (uint)(1 + (((seed >> 5)+1) % ((uint)hashsize-1)));//h2 41 return hashcode; //返回哈希碼 42 } 43 public virtual Object this[Object key] //索引器 44 { 45 get 46 { 47 uint seed; //h1 48 uint incr; //h2 49 uint hashcode = InitHash(key, buckets.Length, 50 out seed, out incr); 51 int ntry = 0; //用於表示h(key,i)中的i值 52 bucket b; 53 int bn = (int)(seed % (uint)buckets.Length); //h(key,0) 54 do 55 { 56 b = buckets[bn]; 57 if (b.key == null) //b爲無衝突空位時 58 { //找不到相應的鍵,返回空 59 return null; 60 } 61 if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && 62 KeyEquals(b.key, key)) 63 { //查找成功 64 return b.val; 65 } 66 bn = (int)(((long)bn + incr) % 67 (uint)buckets.Length); //h(key+i) 68 } while (b.hash_coll < 0 && ++ntry < buckets.Length); 69 return null; 70 } 71 set 72 { 73 Insert(key, value, false); 74 } 75 } 76 private void expand() //擴容 77 { //使新的容量爲舊容量的近似兩倍 78 int rawsize = HashHelpers.GetPrime(buckets.Length * 2); 79 rehash(rawsize); 80 } 81 private void rehash(int newsize) //按新容量擴容 82 { 83 bucket[] newBuckets = new bucket[newsize]; 84 for (int nb = 0; nb < buckets.Length; nb++) 85 { 86 bucket oldb = buckets[nb]; 87 if ((oldb.key != null) && (oldb.key != buckets)) 88 { 89 putEntry(newBuckets, oldb.key, oldb.val, 90 oldb.hash_coll & 0x7FFFFFFF); 91 } 92 } 93 buckets = newBuckets; 94 loadsize = (int)(loadFactor * newsize); 95 return; 96 } 97 //在新數組內添加舊數組的一個元素 98 private void putEntry(bucket[] newBuckets, Object key, 99 Object nvalue, int hashcode) 100 { 101 uint seed = (uint)hashcode; //h1 102 uint incr = (uint)(1 + (((seed >> 5) + 1) % 103 ((uint)newBuckets.Length - 1))); //h2 104 int bn = (int)(seed % (uint)newBuckets.Length);//哈希地址 105 do 106 { //當前位置爲有衝突空位或無衝突空位時都可添加新元素 107 if ((newBuckets[bn].key == null) || 108 (newBuckets[bn].key == buckets)) 109 { //賦值 110 newBuckets[bn].val = nvalue; 111 newBuckets[bn].key = key; 112 newBuckets[bn].hash_coll |= hashcode; 113 return; 114 } 115 //當前位置已存在其他元素時 116 if (newBuckets[bn].hash_coll >= 0) 117 { //置hash_coll的高位爲1 118 newBuckets[bn].hash_coll |= 119 unchecked((int)0x80000000); 120 } 121 //二度哈希h1(key)+h2(key) 122 bn = (int)(((long)bn + incr) % (uint)newBuckets.Length); 123 } while (true); 124 } 125 protected virtual int GetHash(Object key) 126 { //獲取哈希碼 127 return key.GetHashCode(); 128 } 129 protected virtual bool KeyEquals(Object item, Object key) 130 { //用於判斷兩key是否相等 131 return item == null ? false : item.Equals(key); 132 } 133 //當add爲true時用作添加元素,當add爲false時用作修改元素值 134 private void Insert(Object key, Object nvalue, bool add) 135 { //如果超過允許存放元素個數的上限則擴容 136 if (count >= loadsize) 137 { 138 expand(); 139 } 140 uint seed; //h1 141 uint incr; //h2 142 uint hashcode = InitHash(key, buckets.Length,out seed, out incr); 143 int ntry = 0; //用於表示h(key,i)中的i值 144 int emptySlotNumber = -1; //用於記錄空位 145 int bn = (int)(seed % (uint)buckets.Length); //索引號 146 do 147 { //如果是有衝突空位,需繼續向後查找以確定是否存在相同的鍵 148 if (emptySlotNumber == -1 && (buckets[bn].key == buckets) && 149 (buckets[bn].hash_coll < 0)) 150 { 151 emptySlotNumber = bn; 152 } 153 if (buckets[bn].key == null) //確定沒有重複鍵才添加 154 { 155 if (emptySlotNumber != -1) //使用之前的空位 156 bn = emptySlotNumber; 157 buckets[bn].val = nvalue; 158 buckets[bn].key = key; 159 buckets[bn].hash_coll |= (int)hashcode; 160 count++; 161 return; 162 } 163 //找到重複鍵 164 if (((buckets[bn].hash_coll & 0x7FFFFFFF)==hashcode) && 165 KeyEquals(buckets[bn].key, key)) 166 { //如果處於添加元素狀態,則由於出現重複鍵而報錯 167 if (add) 168 { 169 throw new ArgumentException("添加了重複的鍵值!"); 170 } 171 buckets[bn].val = nvalue; //修改批定鍵的元素 172 return; 173 } 174 //存在衝突則置hash_coll的最高位爲1 175 if (emptySlotNumber == -1) 176 { 177 if (buckets[bn].hash_coll >= 0) 178 { 179 buckets[bn].hash_coll |= unchecked((int)0x80000000); 180 } 181 } 182 bn = (int)(((long)bn + incr) % (uint)buckets.Length);//二度哈希 183 } while (++ntry < buckets.Length); 184 throw new InvalidOperationException("添加失敗!"); 185 } 186 public virtual void Remove(Object key) //移除一個元素 187 { 188 uint seed; //h1 189 uint incr; //h2 190 uint hashcode = InitHash(key, buckets.Length,out seed, out incr); 191 int ntry = 0; //h(key,i)中的i 192 bucket b; 193 int bn = (int)(seed % (uint)buckets.Length); //哈希地址 194 do 195 { 196 b = buckets[bn]; 197 if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && 198 KeyEquals(b.key, key)) //如果找到相應的鍵值 199 { //保留最高位,其餘清0 200 buckets[bn].hash_coll &= unchecked((int)0x80000000); 201 if (buckets[bn].hash_coll != 0) //如果原來存在衝突 202 { //使key指向buckets 203 buckets[bn].key = buckets; 204 } 205 else //原來不存在衝突 206 { //置key爲空 207 buckets[bn].key = null; 208 } 209 buckets[bn].val = null; //釋放相應的“值”。 210 count--; 211 return; 212 } //二度哈希 213 bn = (int)(((long)bn + incr) % (uint)buckets.Length); 214 } while (b.hash_coll < 0 && ++ntry < buckets.Length); 215 } 216 public override string ToString() 217 { 218 string s = string.Empty; 219 for (int i = 0; i < buckets.Length; i++) 220 { 221 if (buckets[i].key != null && buckets[i].key != buckets) 222 { //不爲空位時打印索引、鍵、值、hash_coll 223 s += string.Format("{0,-5}{1,-8}{2,-8}{3,-8}\r\n", 224 i.ToString(), buckets[i].key.ToString(), 225 buckets[i].val.ToString(), 226 buckets[i].hash_coll.ToString()); 227 } 228 else 229 { //是空位時則打印索引和hash_coll 230 s += string.Format("{0,-21}{1,-8}\r\n", i.ToString(), 231 buckets[i].hash_coll.ToString()); 232 } 233 } 234 return s; 235 } 236 public virtual int Count //屬性 237 { //獲取元素個數 238 get { return count; } 239 } 240 }
Hashtable和ArrayList的實現有似的地方,比如兩者都是以數組爲基礎做進一步地抽象而來,兩者都可以成倍地自動擴展容量。