哈希表
避免碰撞 — 构造哈希函数、再散列函数法、哈希表加链表
1.虽然很好的处理了碰撞的问题,但是当单链表很长时,遍历链表的速度会很慢。
2.当链表太长时(经验法则:当元素个数比 bucket <vector> 数还要多时),想办法将其打散 — 将 bucket 扩充大约两倍。选择53的倍数附近的素数 53 -> 97 -> 193........。(G2.9适用,其他的不一定。)
3. 哈希表的成长过程:bucket 会增加、 所有的元素所在的 bucket 会重新计算一遍,会花费一定的时间。
4.预先存好要用的素数,需要时直接扩充。{53,97,。。。}
- HashFcn是用来计算元素放的位置,即HashCode。
- ExtractKey用来取出(pair中的)key值的函数。
- EqualKey比较key值相等的函数。
1.hash,equals,get_key是根据传递进来的类型创建出来的三个函数对象。
2.cur指向表中的某一元素,而不是指向buckets。
3.走到一个链表尽头后,要回到哈希表本身,因此需要ht指向buckets,进入下一个篮子。
哈希表本身的大小为20个字节:3个函数对象、1个 Vector、1个 unsigned int。
//Application
hashtable<const char*,
const char*,
hash<const char*>,
identity<const char*>,
eqstr,
alloc>
ht(50,hash<const char*>(),eqstr());
ht.insert_unique("kiwi");
ht.insert_unique("plum");
ht.insert_unique("apple");
struct eqstr{
bool operator()(const char* s1,
const char* s2,) const
{return strcmp(s1,s2) == 0;}
};
这里为什么不直接传入strcmp函数,而要包装成一个eqstr呢?因为此处哈希表中EqualKey要求的是ture or false,而strcmp函数传回的参数是-1,0,1,所以必须加一层外套。
哈希函数,哈希码
泛化 :hash{}
特化:传入的是具体类型,hash函数不一样。
modulus运算(余数运算)
find 和 operator 是哈希表中的一些成员函数,其中要计算元素会落在哪个篮子里。按其调用的路径,最后都会归结到 hash(key)%n 。
篮子数一定比元素数多。
hash_set/hash_multiset
一、概念
- hash_set是以hashtable为底层机制实现的。故对hash_set的各种操作可以转调用hashtable来实现。
- hash_set与set的不同:1)hash_set的底层机制是hashtable,而set的底层机制是RB-tree;2)set的元素能够自动排序,而hash_set的元素没有排序功能。hash_set中键值就是实值,实值就是键值。
- hash_multiset的与hash_set有很大的相同性,其唯一差别就是hash_multiset中的元素可以重复。hash_set中的插入函数使用的是hashtable中的insert_unique(),而hash_multiset中的插入函数使用的是hashtable中的insert_equal()。
二、实现
// hash_set类定义
template <class Value, class HashFcn = hash<Value>,
class EqualKey = equal_to<Value>,
class Alloc = alloc>
class hash_set
{
private:
typedef hashtable<Value, Value, HashFcn, identity<Value>,
EqualKey, Alloc> ht;
ht rep; //底层机制 hashtable
public:
typedef typename ht::key_type key_type;//迭代器声明,用hashtable的
typedef typename ht::value_type value_type;
typedef typename ht::hasher hasher;
typedef typename ht::key_equal key_equal;
typedef typename ht::size_type size_type;
typedef typename ht::difference_type difference_type;
...
hasher hash_funct() const {return rep.hash_funct();}
key_equal key_eq() const {return rep.key_eq();}
public:
hash_set() : rep(100, hasher(), key_equal()) {} //缺省使用100个篮子,
//最后将被调整成对应的质数193
hash_set(size_type n, const hasher& hf) : rep(n, hf, key_equal()) {} //构造函数
hash_set(size_type n, const hasher& hf, const key_equal& eql) //构造函数
: rep(n, hf, eql) {}
//方法,通过hash table实现
size_type size() const { return rep.size(); }
size_type max_size() const { return rep.max_size(); }
bool empty() const { return rep.empty(); }
void swap(hash_set& hs) { rep.swap(hs.rep); }
friend bool operator== __STL_NULL_TMPL_ARGS (const hash_set&,
const hash_set&);
iterator begin() const { return rep.begin(); }
iterator end() const { return rep.end(); }
hash_map/hash_multimap
一、概念
SGI在STL标准外提供了hash_map,以hashtable作为底层机制。运用map为的是能够根据键值快速搜索元素,这一点RB-tree或是hashtable都能够实现。但RB-tree有自动排序能力而hashtable没有,其结果就是set元素会自动排序而hash_set不会。map和hash_map一样,拥有键和值,它们的使用方法也一样。
需要很简单:
1、hash函数(库有则使用库,没有则自己提供仿函数)。
2、key大小比较函数(库有,则使用库提供的模板实例化类,否则自己编写仿函数)
3、使用很简单,插入的时候,注意 insert 和 [ ] 方法的不同。
二.测试hash_map
int main(void){
hash_map<const char * , int , __gnu_cxx::hash<const char*> , eqstr> Mymap;
Mymap["January"] = 31;//设置map特有的操作符重载,下面重点讲解,将hashmap键和值可以用来计算键的次数
Mymap["February"] = 28;
Mymap["March"] = 31;
Mymap["April"] = 30;
Mymap["May"] = 31;
Mymap["June"] = 30;
Mymap["July"] = 31;
Mymap["August"] = 31;
Mymap["September"] = 30;
Mymap["October"] = 31;
Mymap["November"] = 30;
Mymap.insert( pair<const char * , int>("December" , 31) );//产生临时对象,并pair起来
//测试输出对应的键和值
cout << "December " << Mymap["December"] << endl;
cout << "June " << Mymap["June"] << endl;
cout << "April " << Mymap["April"] << endl;
cout << "March " << Mymap["March"] << endl;
cout << endl << "测试遍历操作" << endl;
for(hash_map<const char * , int , __gnu_cxx::hash<const char*> , eqstr>::const_iterator ite = Mymap.begin() ; ite!= Mymap.end() ; ite++)
cout << (*ite).first << " " << ite->second << endl;//记住这两种操作符重载类型
cout << endl;
}
这是最重要的地方,通过key获取对应的value。如果key存在,则返回value的引用,如果key不存在,则插入并且将value初始化为默认值并返回其引用,对应int,就相当于int()那么就是0。
T& operator[](const key_type& key) {
return rep.find_or_insert(value_type(key, T())).second;
}//这个是最重要的方法,区别与set的,很重要,直接调用了find_or_insert
不定序容器初始化时,必须指定 hash_function()。