2009-11-03 39 views
5

我必須承認對HashTables的工作原理只有基本的瞭解,儘管從我所知道的事情來看似乎相當簡單。我的問題是這樣的:傳統的看法似乎是使用簡單的基本值類型,例如HashTable中的鍵的整數。然而,字符串也經常被使用,儘管在很多語言中它們被實現爲引用類型。我覺得一般不是鼓勵使用複雜的參考類型;我猜這是因爲這樣做需要更慢的哈希函數?但那爲什麼如此常用字符串?畢竟,在內部不是一個字符串[]數組(再次,在大多數語言中)?最後,哪些值類型通常被認爲是在HashTable中用作鍵的「最好」(或甚至是「可接受的」)選擇?是否有任何常用的選項被認爲是「不好的」(可能是字符串)?可接受的類型在HashTable中作爲鍵使用

回答

1

最好hash keys是那些

  1. 具有良好的(如在低collisions)哈希值(見Object.GetHashCode爲.NET,Object.hashcode對Java)
  2. 有快速比較(當有哈希衝突的) 。

所有這一切,我認爲在大多數情況下字符串是很好的散列鍵,因爲有很多優秀的字符串散列實現。

3

只要提供了合適的散列函數,所有類型都將作爲關鍵字。畢竟,哈希表只是一個線性數組。哈希函數採用某種類型的鍵並計算哈希表數組(稱爲存儲區)中存儲值的索引(儘管存在一些碰撞問題)。

所以真正棘手的部分是找到一個散列函數。當然,它應該具有某些屬性,如計算簡單,混亂(幾乎相同的鍵應映射到完全不同的哈希表桶),確定性(相同的鍵意味着相同的哈希表桶),一致性(所有可能的鍵均勻映射到桶)或者外觀性(應該使用散列表的所有桶)。

看起來像整數這樣的簡單類型定義這樣一個函數更容易。

+0

錯!真正的問題是關鍵的可變性! – Gyom 2009-11-03 21:06:03

+0

的確如此。然而,它是一個定義什麼鍵被認爲是平等的,哪些不是。 – spa 2009-11-03 21:15:48

4

大多數字符串實現,雖然它們可能在託管環境中顯示爲引用類型,但它們的實現通常是不可變類型。

散列函數的作用是將大量的狀態映射到較少數量的狀態。

這就是爲什麼字符串散列對測試字符串相等性有好處的原因。您可以將該值映射到數組的索引,並快速查找有關該值的一些信息。您不需要將每個字符與其他每個字符串中的其他字符進行比較。你可以說任何事情都是一樣的。這是關於減少或以某種有用的方式指紋任意數量的字節。

這是關於您在哈希表中使用的密鑰類型的討論變得無效的原因,因爲它是將該值映射到較小的狀態空間以及如何在內部使用它以使其有用。一個整數通常是硬件友好的,但32位並不是一個真正的大空間,並且可能在該空間內發生衝突以進行任意輸入。最後,當你確實使用散列表時,計算散列值的代價與將每個值與其他每個可能位置中的每個值進行比較所用的時間無關(假設你的散列值表包含數百個項目)。

+0

我明白散列函數的工作原理是將一個(可能)較大的值映射到一個較小的空間,但散列函數的速度是否也取決於其輸入的大小?這就是爲什麼我認爲它通常不鼓勵使用大型引用類型作爲鍵。不過,如果情況並非如此,那麼我想知道爲什麼這會讓人望而生畏(也許是因爲開發人員需要實現他/她自己的高效散列函數?)。 – 2009-11-03 21:03:12

+0

正如我所說的,許多託管環境將字符串實現爲不可變類型。而當你有一個不可變的類型時,哈希碼不需要每次計算,因爲這個值是常量(一旦創建)。通常,只需爲每個唯一字符串支付一次散列代碼的成本。例如.NET運行時維護一個內部字符串池來完成此操作。但是,從未知字符串生成哈希代碼的代價是存在的,但代價與用作鍵的字符串的長度不相關,而與集合(或哈希表)的大小有關。 – 2009-11-03 21:12:17

+0

這對我來說很不直觀。你是說,如果我將一個項目添加到HashTable中,然後再通過鍵檢索該項目,那麼運行時會奇蹟般地知道該密鑰的哈希碼,而不必執行哈希函數?怎麼會這樣? – 2009-11-03 21:20:09

1

如果你使用一個複雜類型作爲鍵,然後:

  • 這將是很難的哈希表實施,商品組合成桶進行快速檢索;它將如何決定如何將一系列哈希分組到一個桶中?
  • 哈希表可能需要知道該類型以便選擇一個存儲桶。
  • 存在對象屬性更改的風險,導致項目以錯誤桶爲結尾。哈希必須是不可變的。

通常使用整數,因爲它們很容易分割成對應於存儲區的範圍,它們是值類型,因此是不可變的,而且它們相當容易生成。

5

這不是整數或值對比基準的問題,但可變鍵與鍵不變。只要鍵是不可變的(因此它們的散列值永遠不會改變),它們就可以索引一個散列表。例如,Java中的字符串是不可變的,因此非常適合作爲散列表鍵。順便說一句,如果一個數據類型足夠簡單,以便總是按值傳遞(比如標量),那麼它當然是OK的。

但現在想象一下,你使用了一個可變類型;如果你給我一個這些對象的引用作爲關鍵字,我將計算它的散列值,然後把它放在我的一個散列表桶中。但是,當您稍後修改對象時,我將無法通知;並且該對象現在可能駐留在錯誤的存儲桶中(如果其散列值不同)。

希望這會有所幫助。

+0

這是一個非常有用的答案;但我仍然想知道是否有某些類型比其他類型更適合用作鍵。例如,假設我已經定義了一個實際上是不可變的類,並且將爲它的整個存在保留相同的哈希碼。那麼用它作爲關鍵字還是很好,或者使用類似整數的東西(出於性能的原因)它會更好嗎?在我看來,完全的,全面的答案可能是你的組合(鍵必須是不可變的類型),而spa的(用作鍵的類型應該有高效的散列函數)...... – 2009-11-04 00:07:22

+0

@Dan:特定的散列表需要存儲它需要存儲的內容。如果您要維護網頁緩存,則需要爲網址存儲內容。關鍵必須是一個URL,它不能是一個整數,因爲你沒有查找整數。顯然,更快的是「更好」,但「做我想要慢的東西」總是「更好」,而不是「真的很快但完全無用」:-) – 2009-11-04 14:41:01

+0

注意到使用可變類類型沒有任何問題也很重要作爲散列表鍵,如果該鍵的目的是*識別*特定的對象。例如,在.net中,'System.Windows.Forms.Form'是一個非常可變的類型(具有位置等屬性,可隨時更改),但可以使用散列表將關聯表單與其他類型關聯。請注意,這樣的表總是會將不同形式的兩個引用視爲不相等,即使它們的所有屬性都不同於它們的身份匹配。 – supercat 2012-10-12 19:18:06

相關問題