- <p>#include "stdafx.h"
- #include <iostream>
- #include <hash_map>
- #include <vector></p><p>using std::vector;
- using stdext::hash_map;</p><p>class hash_wchar_t
- {
- public:
- // 以下兩個變量我也不是很明白究竟是干嘛的
- static const size_t bucket_size = 4; // 猜測這個變量是初始化hash_map的大小
- static const size_t min_buckets = 8; // 猜測這個變量是每次擴充容量的大小
- // 以上猜測是根據vector得來的,其實我基本上沒使用過STL,只是在C++Primer上看到過,很粗略的看。</p><p> size_t operator()(const wchar_t& GBword) const
- {
- return GBword%100;
- // 下面的那個哈希函數算法是我在網上搜索的說是適合漢字使用的。
- // 具體適不適合我也不知道,這里測試的時候可以用簡單的
- // return ((unsigned char)GBword-176)*94 + (unsigned char)(GBword>>8) - 161;
- } </p><p> bool operator()(const wchar_t& s1,const wchar_t& s2) const
- {
- // copy別人代碼的時候,由於Key類型是char類型字符串,所以是這么寫的
- // return 0 == strcmp(s1,s2);
- // 我針對自己使用的Key類型,在修改了參數的形式之后,很天真的就這么使用,這是問題的關鍵!!
- // 寫成這種形式,在下面 測試能否找到的時候,始終出問題,
- // 原因是p指針指向的是一個未初始化的內存區域,所以無法取數據
- // 具體原理在代碼后面解釋
- return s1 == s2; </p><p> // 最后的正確用法
- // return s1 < s2;
- // 或者 return s2 > s1;
- }
- }; </p><p>
- int main()
- {
- hash_map<const wchar_t,vector<UINT>*,hash_wchar_t> loNameMap;
- vector<UINT>* lpoVecUint = NULL;
- lpoVecUint = new vector<UINT>;
- lpoVecUint->push_back(2);</p><p> loNameMap[L'C'] = lpoVecUint;
- loNameMap[L'A'] = lpoVecUint;
- loNameMap[L'B'] = lpoVecUint;</p><p> vector<UINT>* p = loNameMap[L'A']; // 測試能否找到
- std::cout<<p->size()<<std::endl;
- return 1;
- }
- int main()
- {
- hash_map<const wchar_t,vector<UINT>*> loNameMap;
- vector<UINT>* lpoVecUint = NULL;
- lpoVecUint = new vector<UINT>;
- lpoVecUint->push_back(2);</p><p> loNameMap[L'C'] = lpoVecUint;
- loNameMap[L'A'] = lpoVecUint;
- loNameMap[L'B'] = lpoVecUint;</p><p> vector<UINT>* p = loNameMap[L'A']; // 測試能否找到
- std::cout<<p->size()<<std::endl;
- return 1;
- }</p><p> </p>
代碼部分有很多注釋都是為了增加說明,盡量表現出我調試的過程而加上的(這里說一下,其實是在一位大哥的幫助下,我才理解的)。不理解的可以看完下面我對自定義的哈希函數和比較函數的理解之后再看代碼部分:(這里說明一下,其實對於上面的hash_map,可以不用自定義哈希函數和比較函數)
首先說自定義的哈希函數,通常都是以Key作為參數,通過哈希函數對key值的一系列計算得出一個哈希值,然后hash_map內部會根據這個哈希值將對應的Key_Value對存放在對應的桶中,也就是說,可以看作哈希值就是這個桶的索引。這就是哈希函數為什么能夠使得搜索效率為常數級(注意,是常數級,但是不一定就是1,只是說已經很接近1了)。
然后說關鍵的地方,就是自定義的那個比較函數(就是上面重載()時傳進去兩個參數的那個函數),這里的比較函數,並不是說每次插入,或者使用[]運算符時,都會去調用這個函數和已經存在的元素進行比較(因為如果是的話,凡是牽涉到尋找和插入的操作,都會去調用這個函數了,我之前是以為這個函數是用來判斷兩個Key_Value對是否相等時調用的函數),而是說,當進行尋找和插入操作時,如果新的Key_Value(后面寫成NewValue) 和一個已經存在的Key_Value(后面寫成OldValue)的哈希值相同(這就是哈希沖突),根據上面哈希函數的解釋,這兩個元素就會放置在同一個桶內。也就是說,當NewValue和OldValue的哈希值是一樣,hash_map內部就需要去判斷,應該把這個NewValue舍去、或者是在這個桶的內部,按照Key從小到達排序或者從大到小排序。這個判斷過程,就需要用到這個比較函數了。
這里另起一段說明調用這個比較函數的過程,hash_map每次進入到一個已經存在的桶(因為哈希值已經存在了),就會去這樣調用compare_function(OldValue.Key,NewValue.Key),如果這個函數返回true,它就認為OldValue.Key<NewValue.Key,也就是說,這個比較函數,對於hash_map內部來說,是用來判斷OldValue.Key和NewValue.Key的大小關系的,當返回true的時候,它就認為OldValue.Key<NewValue.Key,當返回false的時候,為了進一步判斷這兩個值的關系,再一次這么調用這個函數compare_function(NewValue.Key,OldValue.Key)。如果還是返回false,說明NewValue.Key不小於OldValue.Key,那么就能確定他們的關系肯定是NewValue.Key==OldValue.Key了。這樣就不會向這個桶中插入這個NewValue了,如果最后確定的關系是NewValue.Key!=OldValue.Key,那么hash_map內部就會根據調用你的這個比較函數得出來 的結果向這個桶子的合適位置插入這個新的元素。
到這里位置,已經講述完了hash_map調用這個比較函數的作用了。為了便於理解寫成偽代碼的形式就是這樣的:
if(!compare_function(OldValue.Key,NewValue.Key))
{ 說明 OldValue.Key !< NewValue.Key;繼續判斷
if(! compare_function(NewValue.Key,OldValue.Key)
{
NewValue.Key !< OldValue.Key,可以確定NewValue.Key ==OldValue.Key;這里就直接返回對OldValue.Value的引用就可以了
}
else
{
確定NewValue.Key < OldValue.Key,接着去和桶內其他的元素去比,最后將這個元素按照桶內從小到大的順序插入
}
}
else
{
確定 OldValue.Key < NewValue.Key; 接着去和桶內其他的元素去比,最后將這個元素按照桶內從小到大的順序插入
}
上面當OldValue.Key != NewValue.Key時,接下來就會再次通過比較函數去尋找一個合適的位置插入,這里的合適的位置,就會因為比較函數的實現邏輯而不同過了,最簡單的方式就是與hash_map內部認為這個函數的功能一致,寫成return s1<s2; hash_map內部通過這個比較函數能夠將桶內的元素按照從小到大的順序排列(確實是從key從小到大的順序),如果寫成了return s1>s2; 因為你的判斷邏輯和哈希函數內部默認的判斷邏輯相反了,最后的結果就是哈希函數把桶內的元素從"小“到”大”排列(而事實上是從大到小). 最后,如果你寫成return s1 == s2,那么當只有當s1確實是等於s2的時候才會返回true,也就是說,只有NewValue確實已經存在的時候,hash_map會在第一次調用比較函數的時候就得到s1<s2的結果,最后實際上是將NewValue插入到OldValue中了,並且,如果NewValue != OldValue的時候,是不會插入新的元素的。
如果比較函數中再寫一些其他的邏輯的話,根據這個思路去判斷,就很容易知道桶內的元素是什么個情況了。
