1. 程式人生 > >阿里面試題 hashtable 如何解決衝突

阿里面試題 hashtable 如何解決衝突

Hashtable的結構,採用的是資料結構中所說的鏈地址法處理衝突的方法 
 
從上面的結構圖可以看出,Hashtable的實質就是一個數組+連結串列。圖中的Entry就是連結串列的實現,Entry的結構中包含了對自己的另一個例項的引用next,用以指向另外一個Entry。而圖中標有數字的部分是一個Entry陣列,數字就是這個Entry陣列的index。那麼往 Hashtable增加鍵值對的時候,index會根據鍵的hashcode、Entry陣列的長度共同決定,從而決定鍵值對存放在Entry陣列的哪個位置。從這種意義來說,當鍵一定,Entry陣列的長度一定的情況下,所得到的index肯定是相同的,也就是說插入順序應該不會影響輸出的順序才對。然而,還有一個重要的因素沒有考慮,就是計算index出現相同值的情況。譬如程式碼中 "sichuan" 和 "anhui",所得到的index是相同的,在這個時候,Entry的連結串列功能就發揮作用了:put方法通過Entry的next屬性獲得對另外一個 Entry的引用,然後將後來者放入其中。根據debug得出的結果,"sichuan", "anhui"的index同為2,"hunan"的index為6,"beijing"的index為1,在輸出的時候,會以index遞減的方式獲得鍵值對。很明顯,會改變的輸出順序只有"sichuan"和"anhui"了,也就是說輸出只有兩種可能:"hunan" - "sichuan" - "anhui" - "beijing"和"hunan" - "anhui" - "sichuan" - "beijing"。以下是運行了示例程式碼之後,Hashtable的結果: 
 
在Hashtable的實現程式碼中,有一個名為rehash的方法用於擴充Hashtable的容量。很明顯,當rehash方法被呼叫以後,每一個鍵值對相應的index也會改變,也就等於將鍵值對重新排序了。這也是往不同容量的Hashtable放入相同的鍵值對會輸出不同的鍵值對序列的原因。在Java中,觸發rehash方法的條件很簡單:hahtable中的鍵值對超過某一閥值。預設情況下,該閥值等於hashtable中Entry陣列的長度×0.75。 (注意entry 數組裡存值了,而傳統hash 連結串列鏈地址法裡沒有存值)



自 Java 2 平臺 v1.2 以來,此類已經改進為可以實現 Map,因此它變成了 Java Collections Framework 的一部分。與新集合的實現不同,Hashtable 是同步的。 

由迭代器返回的 Iterator 和由所有 Hashtable 的“collection 檢視方法”返回的 Collection 的 listIterator 方法都是快速失敗 的:在建立 Iterator 之後,如果從結構上對 Hashtable 進行修改,除非通過 Iterator 自身的移除或新增方法,否則在任何時間以任何方式對其進行修改,Iterator 都將丟擲 ConcurrentModificationException。因此,面對併發的修改,Iterator 很快就會完全失敗,而不冒在將來某個不確定的時間發生任意不確定行為的風險。由 Hashtable 的鍵和值方法返回的 Enumeration 不 是快速失敗的。 

注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步併發修改做出任何硬性保證。快速失敗迭代器會盡最大努力丟擲 ConcurrentModificationException。因此,為提高這類迭代器的正確性而編寫一個依賴於此異常的程式是錯誤做法:迭代器的快速失敗行為應該僅用於檢測程式錯誤。 
直接看HashTable.java
 
解決雜湊表的衝突-開放地址法和鏈地址法 
在實際應用中,無論如何構造雜湊函式,衝突是無法完全避免的。 

1 開放地址法 (顧名思義,可以佔用本來應該其他資料佔用的地址)


這個方法的基本思想是:當發生地址衝突時,按照某種方法繼續探測雜湊表中的其他儲存單元,直到找到空位置為止。這個過程可用下式描述: 
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1)) 
其中: H ( key ) 為關鍵字 key 的直接雜湊地址, m 為雜湊表的長度, di 為每次再探測時的地址增量。 
採用這種方法時,首先計算出元素的直接雜湊地址 H ( key ) ,如果該儲存單元已被其他元素佔用,則繼續檢視地址為 H ( key ) + d 2 的儲存單元,如此重複直至找到某個儲存單元為空時,將關鍵字為 key 的資料元素存放到該單元。
增量 d 可以有不同的取法,並根據其取法有不同的稱呼: 
( 1 ) d i = 1 , 2 , 3 , …… 線性探測再雜湊; 
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探測再雜湊; 
( 3 ) d i = 偽隨機序列 偽隨機再雜湊; 

例1設有雜湊函式 H ( key ) = key mod 7 ,雜湊表的地址空間為 0 ~ 6 ,對關鍵字序列( 32 , 13 , 49 , 55 , 22 , 38 , 21 )按線性探測再雜湊和二次探測再雜湊的方法分別構造雜湊表。 
解: 
( 1 )線性探測再雜湊: 
32 % 7 = 4 ; 13 % 7 = 6 ; 49 % 7 = 0 ; 
55 % 7 = 6 發生衝突,下一個儲存地址( 6 + 1 )% 7 = 0 ,仍然發生衝突,再下一個儲存地址:( 6 + 2 )% 7 = 1 未發生衝突,可以存入。 
22 % 7 = 1 發生衝突,下一個儲存地址是:( 1 + 1 )% 7 = 2 未發生衝突; 
38 % 7 = 3 ; 
21 % 7 = 0 發生衝突,按照上面方法繼續探測直至空間 5 ,不發生衝突,所得到的雜湊表對應儲存位置: 
下標: 0 1 2 3 4 5 6 
49 55 22 38 32 21 13 
( 2 )二次探測再雜湊: 
下標: 0 1 2 3 4 5 6 
49 22 21 38 32 55 13 
   注意:對於利用開放地址法處理衝突所產生的雜湊表中刪除一個元素時需要謹慎,不能直接地刪除,因為這樣將會截斷其他具有相同雜湊地址的元素的查詢地址,所以,通常採用設定一個特殊的標誌以示該元素已被刪除。 
2 鏈地址法 

鏈地址法解決衝突的做法是:如果雜湊表空間為 0 ~ m - 1 ,設定一個由 m 個指標分量組成的一維陣列 ST[ m ], 凡雜湊地址為 i 的資料元素都插入到頭指標為 ST[ i ] 的連結串列中。這種方法有點近似於鄰接表的基本思想,且這種方法適合於衝突比較嚴重的情況。 

例 2 設有 8 個元素 { a,b,c,d,e,f,g,h } ,採用某種雜湊函式得到的地址分別為: {0 , 2 , 4 , 1 , 0 , 8 , 7 , 2} ,當雜湊表長度為 10 時,採用鏈地址法解決衝突的雜湊表如下圖所示。