雪竹的天空

theorix

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  34 随笔 :: 0 文章 :: 20 评论 :: 0 Trackbacks

1 数据结构:hash_map原理

这是一节让你深入理解hash_map的介绍,如果你只是想囫囵吞枣,不想理解其原理,你倒是可以略过这一节,但我还是建议你看看,多了解一些没有坏处。

hash_map基于hash table(哈希表)。 哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。

其基本原理是:使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数,也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标,hash值)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方,称为桶。

但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。 总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。

hash_map,首先分配一大片内存,形成许多桶。是利用hash函数,对key进行映射到不同区域(桶)进行保存。其插入过程是:

  1. 得到key
  2. 通过hash函数得到hash值
  3. 得到桶号(一般都为hash值对桶数求模)
  4. 存放key和value在桶内。
其取值过程是:
  1. 得到key
  2. 通过hash函数得到hash值
  3. 得到桶号(一般都为hash值对桶数求模)
  4. 比较桶的内部元素是否与key相等,若都不相等,则没有找到。
  5. 取出相等的记录的value。
hash_map中直接地址用hash函数生成,解决冲突,用比较函数解决。这里可以看出,如果每个桶内部只有一个元素,那么查找的时候只有一次比较。当许多桶内没有值时,许多查询就会更快了(指查不到的时候).

由此可见,要实现哈希表, 和用户相关的是:hash函数和比较函数。这两个参数刚好是我们在使用hash_map时需要指定的参数。

2 hash_map 使用

2.1 一个简单实例

不要着急如何把"岳不群"用hash_map表示,我们先看一个简单的例子:随机给你一个ID号和ID号相应的信息,ID号的范围是1~2的31次方。如何快速保存查找。
#include <hash_map>
                                    #include <string>
                                    using namespace std;
                                    int main(){
                                    hash_map<int, string> mymap;
                                    mymap[9527]="唐伯虎点秋香";
                                    mymap[1000000]="百万富翁的生活";
                                    mymap[10000]="白领的工资底线";
                                    ...
                                    if(mymap.find(10000) != mymap.end()){
                                    ...
                                    }

                                    
够简单,和map使用方法一样。这时你或许会问?hash函数和比较函数呢?不是要指定么?你说对了,但是在你没有指定hash函数和比较函数的时候,你会有一个缺省的函数,看看hash_map的声明,你会更加明白。下面是SGI STL的声明:
template <class _Key, class _Tp, class _HashFcn = hash<_Key>,
                                    class _EqualKey = equal_to<_Key>,
                                    class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
                                    class hash_map
                                    {
                                    ...
                                    }

                                    
也就是说,在上例中,有以下等同关系:
...
                                    hash_map<int, string> mymap;
                                    //等同于:
                                    hash_map<int, string, hash<int>, equal_to<int> > mymap;

                                    
Alloc我们就不要取关注太多了(希望深入了解Allocator的朋友可以参看标准库 STL :Allocator能做什么)

2.2 hash_map 的hash函数

hash< int>到底是什么样子?看看源码:
struct hash<int> {
                                    size_t operator()(int __x) const { return __x; }
                                    };

                                    
原来是个函数对象。在SGI STL中,提供了以下hash函数:
struct hash<char*>
                                    struct hash<const char*>
                                    struct hash<char>
                                    struct hash<unsigned char>
                                    struct hash<signed char>
                                    struct hash<short>
                                    struct hash<unsigned short>
                                    struct hash<int>
                                    struct hash<unsigned int>
                                    struct hash<long>
                                    struct hash<unsigned long> 

                                    
也就是说,如果你的key使用的是以上类型中的一种,你都可以使用缺省的hash函数。当然你自己也可以定义自己的hash函数。对于自定义变量,你只能如此,例如对于string,就必须自定义hash函数。例如:
struct str_hash{
                                    size_t operator()(const string& str) const
                                    {
                                    unsigned long __h = 0;
                                    for (size_t i = 0 ; i < str.size() ; i ++)
                                    __h = 5*__h + str[i];
                                    return size_t(__h);
                                    }
                                    };
                                    //如果你希望利用系统定义的字符串hash函数,你可以这样写:
                                    struct str_hash{
                                    size_t operator()(const string& str) const
                                    {
                                    return return __stl_hash_string(str.c_str());
                                    }
                                    };

                                    
在声明自己的哈希函数时要注意以下几点:
  1. 使用struct,然后重载operator().
  2. 返回是size_t
  3. 参数是你要hash的key的类型。
  4. 函数是const类型的。
如果这些比较难记,最简单的方法就是照猫画虎,找一个函数改改就是了。

现在可以对开头的"岳不群"进行哈希化了 smile . 直接替换成下面的声明即可:

map<string, string> namemap;
                                    //改为:
                                    hash_map<string, string, str_hash> namemap;

                                    
其他用法都不用边。当然不要忘了吧str_hash的声明以及头文件改为hash_map。

你或许会问:比较函数呢?别着急,这里就开始介绍hash_map中的比较函数。

2.3 hash_map 的比较函数

在map中的比较函数,需要提供less函数。如果没有提供,缺省的也是less< Key> 。在hash_map中,要比较桶内的数据和key是否相等,因此需要的是是否等于的函数:equal_to< Key> 。先看看equal_to的源码:
//本代码可以从SGI STL
                                    //先看看binary_function 函数声明,其实只是定义一些类型而已。
                                    template <class _Arg1, class _Arg2, class _Result>
                                    struct binary_function {
                                    typedef _Arg1 first_argument_type;
                                    typedef _Arg2 second_argument_type;
                                    typedef _Result result_type;
                                    };
                                    //看看equal_to的定义:
                                    template <class _Tp>
                                    struct equal_to : public binary_function<_Tp,_Tp,bool>
                                    {
                                    bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; }
                                    };

                                    
如果你使用一个自定义的数据类型,如struct mystruct, 或者const char* 的字符串,如何使用比较函数?使用比较函数,有两种方法. 第一种是:重载==操作符,利用equal_to;看看下面的例子:
struct mystruct{
                                    int iID;
                                    int  len;
                                    bool operator==(const mystruct & my) const{
                                    return (iID==my.iID) && (len==my.len) ;
                                    }
                                    };  

                                    
这样,就可以使用equal_to< mystruct>作为比较函数了。另一种方法就是使用函数对象。自定义一个比较函数体:
struct compare_str{
                                    bool operator()(const char* p1, const char*p2) const{
                                    return strcmp(p1,p2)==0;
                                    }
                                    };  

                                    
有了compare_str,就可以使用hash_map了。
typedef hash_map<const char*, string, hash<const char*>, compare_str> StrIntMap;
                                    StrIntMap namemap;
                                    namemap["岳不群"]="华山派掌门人,人称君子剑";
                                    namemap["张三丰"]="武当掌门人,太极拳创始人";
                                    namemap["东方不败"]="第一高手,葵花宝典";

                                    

2.4 hash_map 函数

hash_map的函数和map的函数差不多。具体函数的参数和解释,请参看:STL 编程手册:Hash_map,这里主要介绍几个常用函数。
  1. hash_map(size_type n) 如果讲究效率,这个参数是必须要设置的。n 主要用来设置hash_map 容器中hash桶的个数。桶个数越多,hash函数发生冲突的概率就越小,重新申请内存的概率就越小。n越大,效率越高,但是内存消耗也越大。
  2. const_iterator find(const key_type& k) const. 用查找,输入为键值,返回为迭代器。
  3. data_type& operator[](const key_type& k) . 这是我最常用的一个函数。因为其特别方便,可像使用数组一样使用。不过需要注意的是,当你使用[key ]操作符时,如果容器中没有key元素,这就相当于自动增加了一个key元素。因此当你只是想知道容器中是否有key元素时,你可以使用find。如果你希望插入该元素时,你可以直接使用[]操作符。
  4. insert 函数。在容器中不包含key值时,insert函数和[]操作符的功能差不多。但是当容器中元素越来越多,每个桶中的元素会增加,为了保证效率,hash_map会自动申请更大的内存,以生成更多的桶。因此在insert以后,以前的iterator有可能是不可用的。
  5. erase 函数。在insert的过程中,当每个桶的元素太多时,hash_map可能会自动扩充容器的内存。但在sgi stl中是erase并不自动回收内存。因此你调用erase后,其他元素的iterator还是可用的。

3 相关hash容器

hash 容器除了hash_map之外,还有hash_set, hash_multimap, has_multiset, 这些容器使用起来和set, multimap, multiset的区别与hash_map和map的区别一样,我想不需要我一一细说了吧。

4 其他

这里列几个常见问题,应该对你理解和使用hash_map比较有帮助。

4.1 hash_map和map的区别在哪里?

  • 构造函数。hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).
  • 存储结构。hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。因此其memory数据结构是不一样的。

4.2 什么时候需要用hash_map,什么时候需要用map?

总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且hash_map的构造速度较慢。

现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用。

这里还有个关于hash_map和map的小故事,看看:http://dev.csdn.net/Develop/article/14/14019.shtm

4.3 如何在hash_map中加入自己定义的类型?

你只要做两件事, 定义hash函数,定义等于比较函数。下面的代码是一个例子:
-bash-2.05b$ cat my.cpp
                                    #include <hash_map>
                                    #include <string>
                                    #include <iostream>
                                    using namespace std;
                                    //define the class
                                    class ClassA{
                                    public:
                                    ClassA(int a):c_a(a){}
                                    int getvalue()const { return c_a;}
                                    void setvalue(int a){c_a;}
                                    private:
                                    int c_a;
                                    };
                                    //1 define the hash function
                                    struct hash_A{
                                    size_t operator()(const class ClassA & A)const{
                                    //  return  hash<int>(classA.getvalue());
                                    return A.getvalue();
                                    }
                                    };
                                    //2 define the equal function
                                    struct equal_A{
                                    bool operator()(const class ClassA & a1, const class ClassA & a2)const{
                                    return  a1.getvalue() == a2.getvalue();
                                    }
                                    };
                                    int main()
                                    {
                                    hash_map<ClassA, string, hash_A, equal_A> hmap;
                                    ClassA a1(12);
                                    hmap[a1]="I am 12";
                                    ClassA a2(198877);
                                    hmap[a2]="I am 198877";
                                    cout<<hmap[a1]<<endl;
                                    cout<<hmap[a2]<<endl;
                                    return 0;
                                    }
                                    -bash-2.05b$ make my
                                    c++  -O -pipe -march=pentiumpro  my.cpp  -o my
                                    -bash-2.05b$ ./my
                                    I am 12
                                    I am 198877

                                    
typedef map<Key, Value> KeyMap;

                                    

当你希望使用hash_map来替换的时候,只需要修改:

typedef hash_map<Key, Value> KeyMap;

                                    

其他的基本不变。当然,你需要注意是否有Key类型的hash函数和比较函数。

 

 

 

//////////////////////////////////////

参数使用说明

来源http://www.stlchina.org/twiki/bin/view.pl/Main/STLHashMap

/////////////////////////////////////////////

hash_map<Key, Data, HashFcn, EqualKey, Alloc>

Category: containers

Description

Hash_map 是一种使用hash 关联容器,把Key 和value数据对应存储; Hash_map 同样是一个Pair 的关联容器,这意味着其元素类型是pair<const Key, Data>; Hash_map 还是Unique 关联容器,即使用EqualKey比较函数来判断不存在两个元素的key值相等。

由于hash_map在通过key值查找时具有很高的效率,所以hash_map对于一些互不相干的元素的存储非常有用。如果元素的某种顺序比较重要,使用map更合适一些。

Example

struct eqstr
                                    {
                                    bool operator()(const char* s1, const char* s2) const
                                    {
                                    return strcmp(s1, s2) == 0;
                                    }
                                    };
                                    int main()
                                    {
                                    hash_map<const char*, int, hash<const char*>, eqstr> months;
                                    months["january"] = 31;
                                    months["february"] = 28;
                                    months["march"] = 31;
                                    months["april"] = 30;
                                    months["may"] = 31;
                                    months["june"] = 30;
                                    months["july"] = 31;
                                    months["august"] = 31;
                                    months["september"] = 30;
                                    months["october"] = 31;
                                    months["november"] = 30;
                                    months["december"] = 31;
                                    cout << "september -> " << months["september"] << endl;
                                    cout << "april     -> " << months["april"] << endl;
                                    cout << "june      -> " << months["june"] << endl;
                                    cout << "november  -> " << months["november"] << endl;
                                    }

                                    

Definition Defined in the header hash_map, and in the backward-compatibility header hash_map.h. This class is an SGI extension; it is not part of the C++ standard.

Template parameters

 

 

 

 

 

 

Parameter Description

 

Default
Key The hash_map's key type. This is also defined as hash_map::key_type.  

 

Data The hash_map's data type. This is also defined as hash_map::data_type.  
HashFcn The hash function used by the hash_map. This is also defined as hash_map::hasher. hash<Key>
EqualKey The hash_map key equality function: a binary predicate that determines whether two keys are equal. This is also defined as hash_map::key_equal. equal_to<Key>

 

Alloc The hash_map's allocator, used for all internal memory management. alloc

Members

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Member Where defined Description
key_type Associative Container The hash_map's key type, Key.

 

data_type Pair Associative Container The type of object associated with the keys.
value_type Pair Associative Container The type of object, pair<const key_type, data_type>, stored in the hash_map.
hasher Hashed Associative Container The hash_map's hash function.
key_equal Hashed Associative Container Function object that compares keys for equality.
pointer Container Pointer to T.
reference Container Reference to T

const_reference

Container Const reference to T
size_type

 

Container An unsigned integral type.
difference_type Container

 

A signed integral type.
iterator Container Iterator used to iterate through a hash_map. [1]

 

const_iterator Container Const iterator used to iterate through a hash_map.
iterator begin() Container Returns an iterator pointing to the beginning of the hash_map.

 

iterator end() Container Returns an iterator pointing to the end of the hash_map.

 

const_iterator begin() const Container Returns an const_iterator pointing to the beginning of the hash_map.

 

const_iterator end() const Container Returns an const_iterator pointing to the end of the hash_map.

 

size_type size() const Container Returns the size of the hash_map.
size_type max_size() const Container Returns the largest possible size of the hash_map.
bool empty() const Container true if the hash_map's size is 0.

 

size_type bucket_count() const Hashed Associative Container Returns the number of buckets used by the hash_map.
void resize(size_type n) Hashed Associative Container Increases the bucket count to at least n.
hasher hash_funct() const Hashed Associative Container Returns the hasher object used by the hash_map.

 

key_equal key_eq() const Hashed Associative Container Returns the key_equal object used by the hash_map.

 

hash_map() Container Creates an empty hash_map.
hash_map(size_type n) Hashed Associative Container Creates an empty hash_map with at least n buckets.

 

hash_map(size_type n,
                                                const hasher& h)
                                                
Hashed Associative Container Creates an empty hash_map with at least n buckets, using h

as the hash function.

hash_map(size_type n,
                                                const hasher& h,
                                                const key_equal& k)
                                                
Hashed Associative Container

 

Creates an empty hash_map with at least n buckets, using h as the hash function and k as the key equal function.
template <class InputIterator>
                                                hash_map(InputIterator f, InputIterator l)
                                                [2]
                                                
Unique Hashed Associative Container

 

Creates a hash_map with a copy of a range.
template <class InputIterator>
                                                hash_map(InputIterator f, InputIterator l,
                                                size_type n)
                                                [2]
                                                
Unique Hashed Associative Container Creates a hash_map with a copy of a range and a bucket count of at least n.
template <class InputIterator>
                                                hash_map(InputIterator f, InputIterator l,
                                                size_type n, const hasher& h)
                                                [2]
                                                
Unique Hashed Associative Container Creates a hash_map with a copy of a range and a bucket count of at least n, using h as the hash function.

 

template <class InputIterator>
                                                hash_map(InputIterator f, InputIterator l,
                                                size_type n, const hasher& h,
                                                const key_equal& k)
                                                [2]
                                                

 

Unique Hashed Associative Container Creates a hash_map with a copy of a range and a bucket count of at least n, using h as the hash function and k as the key equal function.
hash_map(const hash_map&) Container The copy constructor.
hash_map& operator=(const hash_map&) Container The assignment operator
void swap(hash_map&) Container Swaps the contents of two hash_maps.
pair<iterator, bool>
                                                insert(const value_type& x)
                                                
Unique Associative Container Inserts x into the hash_map.

 

template <class InputIterator>
                                                void insert(InputIterator f, InputIterator l)
                                                [2]
                                                
Unique Associative Container

 

Inserts a range into the hash_map.
void erase(iterator pos) Associative Container Erases the element pointed to by pos.
size_type erase(const key_type& k) Associative Container

 

Erases the element whose key is k.
void erase(iterator first, iterator last) Associative Container Erases all elements in a range.
void clear() Associative Container Erases all of the elements.
const_iterator find(const key_type& k) const Associative Container Finds an element whose key is k.

 

iterator find(const key_type& k) Associative Container Finds an element whose key is k.

 

size_type count(const key_type& k) const Unique Associative Container Counts the number of elements whose key is k.

 

pair<const_iterator, const_iterator>
                                                equal_range(const key_type& k) const
                                                
Associative Container

 

Finds a range containing all elements whose key is k.
pair<iterator, iterator>
                                                equal_range(const key_type& k)
                                                

 

Associative Container Finds a range containing all elements whose key is k.
data_type&
                                                operator[](const key_type& k) [3]
                                                
hash_map See below.

 

bool operator==(const hash_map&,
                                                const hash_map&)
                                                
Hashed Associative Container Tests two hash_maps for equality. This is a global function, not a member function.

New members

These members are not defined in the Unique Hashed Associative Container and Pair Associative Container requirements, but are specific to hash_map.

 

 

 

Member Description
data_type&
                                                operator[](const key_type& k) [3]
                                                
Returns a reference to the object that is associated with a particular key. If the hash_map does not already contain such an object, operator[] inserts the default object data_type(). [3]

 

Notes

[1] Hash_map::iterator is not a mutable iterator, because hash_map::value_type is not Assignable. That is, if i is of type hash_map::iterator and p is of type

hash_map::value_type, then *i = p is not a valid expression. However, hash_map::iterator isn't a constant iterator either, because it can be used to modify the object that it points to. Using the same notation as above, (*i).second = p is a valid expression.

[2] This member function relies on member template functions, which at present (early 1998) are not supported by all compilers. If your compiler supports member templates, you can call this function with any type of input iterator. If your compiler does not yet support member templates, though, then the arguments must either be of type const value_type* or of type

hash_map::const_iterator.

[3] Since operator[] might insert a new element into the hash_map, it can't possibly be a const member function. Note that the definition of operator[] is extremely simple: m[k] is equivalent to

(*((m.insert(value_type(k, data_type()))).first)).second. Strictly speaking, this member function is unnecessary: it exists only for convenience.

posted on 2008-09-08 23:23 雪竹的天空( theorix ) 阅读(6964) 评论(0)  编辑 收藏 引用 所属分类: 算法资料

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理