最近申请加入学校的机器人足球实验室,被要求写一个hash_map容器类,接口与STL相似。键的数据类型为string,值类型可以为内置类型或自定义。
myHashMap说明文档
一、设计思想:
1 哈希函数的设计: 将字符串按照“按位加权”,然后对哈希表大小取模将其映射到表中。
2解决冲突:采用开链法,发生冲突时将其放到对应的链表中。
3减少冲突:哈希表的大小取素数。 当元素个数/哈希表大小 > 0.75 时,扩大哈希表的大小,重建哈希表。
二、接口:
接口基本按照STL map的标准,但没有实现迭代器。
1 myHashNode<T> * begin() const ;
2 myHashNode<T> * end( ) const ; //超出末端指针
4 unsigned long size() const ;//哈希表已有的元素的个数
5 T get( myHashNode<T> * cur) const; //返回cur指针所指的值
6 unsigned long hash_function( string str , unsigned long SIZE) ;// 作用:对字符串str 返回哈希函数值
7 void insert( string str, T value ) ; // 插入关键字为str,值为value的组
8 myHashNode<T>* find( string str ) ; //查找关键字为str的组,返回其指针 ,若查找不到则返回“超出末端”的指针
9 void erase( string str ) ; //删除关键字为str的组
10 void resize( ) ; //扩大哈希表的容量
11 bool empty( ) ;//判断哈希表是否为空
12 void clear( ) ; //清空哈希表的所有元素
13 T& operator[] ( string str ) ; //重载[]运算符 ,若[]中的key不存在,则在表中插入一个关键字为key的新元素(保持与 STL的接口一致)
使用方法时与STL中的map基本一致。
声明hash_map 可以使用:
myHashMap< int > imap ;
myHashMap< point > pmap ;
而对容器的赋值可以使用下标如下:
imap[ str ] = 8;
查找键值为str的元组的值可以使用:
myHashMap * cur = imap.find( str ) ;
判断表中是否含键值为str的元组可以使用:
if ( imap.find( str ) == imap.end( ) ) //条件成立表明表中不含键值为str的组
判断哈希表是否为空可以使用:
if( imap.empty( ) ) //条件成立表明哈希表为空
而清空哈希表可以使用:
imap.clear( )
三、测试
我实现的这个hash_map 的键值是string类型的,而对应的值可以使各种类型的。
我用程序产生了2万多组随机的测试数据(见test_1.txt和test_2.txt,test.cpp为测试程序)测试了对应值类型为 int 和自定义的class类型两种情况,感觉查找、插入、删除的速度实在太快了,完全感觉不到延迟。
参考资料:《STL源码剖析》 侯捷 华中科技大学出版社 2002年
"my_hash_map.h"源代码:
Code
/**//* 作者:heaad
* email: heaad#qq.com
* 2009年5月16
* 本程序是哈希表实现的 hash_map容器类,接口与STL hash_map类似
*/
#ifndef __my_hash_map__
#define __my_hash_map__
#include<string>
#include<algorithm> //需要lower_bound函数
//负载系数,当哈希表中元素个数/哈希表容量 > 负载系数 时将重建更大的哈希表
const double __load_factor = 0.75 ;
//素数表的大小
const int __num_prime = 28 ;
// 0至UINT_MAX内部分素数,哈希表的大小为素数可以减少哈希函数的冲突
const unsigned long __prime_list [ __num_prime ] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473ul, 4294967291ul
};
//在 __prime_list 中二分查找第一个不小于n 的素数,若查找不到则返回最后一个
inline unsigned long __next_prime ( unsigned long n )
{
const unsigned long * first = __prime_list ;
const unsigned long * last = __prime_list + __num_prime ;
const unsigned long * pos = lower_bound( first , last , n ) ;
return ( pos == last )? *( last - 1 ) : *pos ;
}
template <class T>
class myHashNode
{
public:
myHashNode()
{
key = "" ;
used = false ;
nextBucket = NULL ;
}
myHashNode( string str, T v )
{
key = str ;
value = v ;
nextBucket = NULL ;
used = true ;
}
bool isUsed() { return used ; }
void setUsed() { used = true ; }
string key ;
T value ;
myHashNode * nextBucket ;
bool used ;
};
template <class T>
class myHashMap
{
public:
myHashMap()
{
hash = new myHashNode<T> [ __prime_list[0] ] ;
__size = __prime_list[0] ;
__num_node = 0 ;
}
myHashNode<T> * begin() const { return hash ; }
myHashNode<T> * end( ) const { return hash + __size ; }
unsigned long capaticy() const { return __size ; }//返回哈希表的容量
unsigned long size() const { return __num_node ; }//哈希表已有的元素的个数
T get( myHashNode<T> * cur) const { return cur->value ; }
unsigned long hash_function( string str , unsigned long SIZE) ;// 作用:对字符串str 返回哈希函数值
void insert( string str, T value ) ; // 插入关键字为str,值为value的组
myHashNode<T>* find( string str ) ; //查找关键字为str的组,返回其指针 ,若查找不到则返回“超出末端”的指针
void erase( string str ) ; //删除关键字为str的组
void resize( ) ; //扩大哈希表的容量
bool empty( ) { return __num_node == 0 ; } //判断哈希表是否为空
void clear( ) ; //清空哈希表的所有元素
T& operator[] ( string str ) ; //重载[]运算符 ,若[]中的key不存在,则在表中插入一个关键字为key的新元素
private:
myHashNode<T> *hash ;
unsigned long __size ;
unsigned long __num_node ;
};
/**//* 作用:对字符串str 返回哈希函数值 */
/**//* 哈希函数值 = 256^(n-1)* (str[0]-'0') + 256^(n-2) *(str[1]-'0') + …… +256^0 *(str[n-1]-'0' ) MOD SIZE */
template<class T>
unsigned long myHashMap<T>::hash_function( string str , unsigned long SIZE)
{
unsigned long uper = UINT_MAX / 256 ;
unsigned long temp = 0 ;
unsigned long last = str.size() - 1 ;
while( last !=0 )
{
temp *= 256 ;
temp += str[ last ] ;
if( temp >= uper ) //确保不超出Unsigned long 的范围
temp %= SIZE ;
--last ;
}
temp *= 256 ;
temp += str[0] ;
temp %= SIZE ;
return temp ;
}
/**//*查找关键字为str的组,返回其指针 ,若查找不到则返回“超出末端”的指针 */
template<class T>
myHashNode<T>* myHashMap<T>::find( string str )
{
unsigned long pos = hash_function( str , __size ) ;
if( hash[ pos ].isUsed() == true && hash[ pos ].key == str )
return hash + pos ;
myHashNode<T> * cur = hash[pos].nextBucket ;
while( cur != NULL )
{
if( cur->isUsed() && cur->key == str )
return cur ;
cur = cur->nextBucket ;
}
return hash + __size ;
}
template<class T>
void myHashMap<T>::insert( string str, T v ) // 插入关键字为str,值为v的组
{
if( (float)__num_node/(float)__size > __load_factor ) //当前容量已经超过负载系数,所以重建哈希表
resize( ) ;
++ __num_node ;
unsigned long pos = hash_function( str , __size ) ;
if( hash[ pos].used == false || hash[pos].key == str ) //覆盖旧值
{
hash[ pos ].key = str ;
hash[ pos ].value = v ;
hash[ pos ].used = true ;
return ;
}
myHashNode<T> * cur = hash[ pos ].nextBucket ;
if( cur == NULL )
{
myHashNode<T> * newnode = new myHashNode<T>( str , v ) ;
hash[ pos ].nextBucket = newnode ;
return ;
}
while( cur->nextBucket != NULL && cur->nextBucket->key != str && cur->nextBucket->used == true )
cur = cur->nextBucket ;
if( cur->nextBucket == NULL )
{
myHashNode<T> * newnode = new myHashNode<T>( str , v ) ;
cur->nextBucket = newnode ;
return ;
}
if( cur->nextBucket->used == false ) //找到一个已经被删除的元组,覆盖旧关键字和旧值
{
cur->nextBucket->key = str ;
cur->value = v ;
cur->used = true ;
return ;
}
cur->nextBucket->value = v ; //查找到关键字相同的组,覆盖旧值
return ;
}
/**//*扩大哈希表的容量 */
/**//*先分配一块更大的空间,然后将原表中的元素逐一哈希到新表中 */
template <class T>
void myHashMap<T>::resize( )
{
unsigned long newSize= __next_prime( 2*__num_node) ; //增大容量
myHashNode<T> * t = new myHashNode<T>[ newSize ] ;
unsigned long i ;
for( i = 0 ; i< __size ; ++i )
{
if( hash[i].isUsed() )
{
//计算新的哈希函数值 ,然后插入新表中
unsigned long pos = hash_function( hash[i].key ,newSize ) ;
//新表中对应的位置未被占据
if( !t[pos].isUsed() )
{
t[pos].key = hash[i].key ;
t[pos].value = hash[i].value ;
t[pos].setUsed() ;
}
else //已被占据,插入到链表中
{
myHashNode<T> * cur = &t[pos] ;
while( cur->nextBucket !=NULL ) //由于是新表,所以链表中不存在被标志为删除的节点
cur = cur->nextBucket ;
myHashNode<T> *newNode = new myHashNode<T>( hash[i].key , hash[i].value ) ;
cur->nextBucket = newNode ;
}
}
//将原链表中的元素插入新的哈希表
myHashNode<T> * c = hash[i].nextBucket ;
while( c !=NULL )
{
if( c->isUsed() )
{
unsigned long pos = hash_function( c->key ,newSize ) ;
//新表中对应的位置未被占据
if( !t[pos].isUsed() )
{
t[pos].key = c->key ;
t[pos].value = c->value ;
t[pos].setUsed() ;
}
else //已被占据,插入到链表中
{
myHashNode<T> * cur = &t[pos] ;
while( cur->nextBucket !=NULL ) //由于是新表,所以链表中不存在被标志为删除的节点
cur = cur->nextBucket ;
myHashNode<T> *newNode = new myHashNode<T>( c->key , c->value ) ;
cur->nextBucket = newNode ;
}
}
c = c->nextBucket ;
}
}
//回收原哈希表的内存空间
clear( ) ;//删除连接的所有链表
delete []hash ; //释放旧空间,指向新的空间
hash = t ;
__size = newSize ;
}
/**//*删除关键字为str的组 */
template< class T >
void myHashMap<T>::erase( string str )
{
myHashNode<T> * cur = find( str ) ; //先检查str是否存在于表中
if( cur == end() )
return ;
-- __num_node ;
cur->used = false ; //并不删除,只是将该节点设置为未被使用的状态
return ;
}
/**//* 重载[] 运算符 */
/**//* 若 []中的key不存在,则在表中插入一个关键字为key的新元素 */
template<class T>
T& myHashMap<T>::operator[] ( string str )
{
myHashNode<T> *cur = find( str ) ;
if( cur != end() )
return cur->value ;
else
{
T *temp = new T ; //这里注意要用new,否则会出现未初始化的错误
insert( str , *temp );
cur = find( str ) ;
return cur->value ;
}
}
/**//* 清空哈希表的所有元素 */
/**//* 实现方法:将所有链表删除, 数组hash 上的元素则设置为未被使用 */
template< class T >
void myHashMap<T>::clear( )
{
myHashNode<T> * cur = NULL ;
myHashNode<T> * next = NULL ;
for(unsigned int i=0 ; i<__size ; ++i ) //删除挂接的所有链表
{
hash[ i ].used = false ;
cur = hash[i].nextBucket;
next = cur ;
while( cur != NULL && next != NULL )
{
next = cur->nextBucket ;
delete cur ;
cur = next ;
}
}
__num_node = 0 ;
}
#endif
源代码、测试程序、测试数据下载:源代码与说明文档.rar