STL源码剖析之关联式容器底层的又一实现:Hash
2011-06-29 20:43 Aga.J 阅读(873) 评论(0) 编辑 收藏 举报281 hastable
二叉搜索树再插入,删除,查找具有对数的平均时间,但是它是基于这样的假设,插入的数据具有一定的随机性,不然的话,如果是有序元素的插入,会使得二叉搜索树严重不平衡。
而hashtable这种数据结构,在插入,删除,查找也有常数平均时间,而不依赖于插入元素的随机性,是以统计为基础的。
Hash table可以提供对任何named item的存取和删除操作,因为所有被操作的对象都是named,所以hash table可以看做是一个dictionary。
如何避免使用一个大得换谬的array呢,办法之一就是使用某种映射函数,将大数映射为小数,这样一来,array就不需要特别的大,但是可能会造成冲突。而解决这种冲突的做法则是使用 线性探测,二次探测,开放寻址等。
SGI的hash table使用的是开链 separate chaining
Template<class Value>
Struct __hashtable_node
{
__hashtable_node* next ;
Value val ;
} ;
Bucket所维护的linked list不采用STL的list或者slist(直接指针操作),而至于bucket则使用vector来完成
282 hashtable的迭代器
Template<class Value, class Key, class HashFun, class ExtractKey, class EqualKey, class Alloc>
Struct __hashtable_iterator
{
Typedef hashtable<value,key,hashfun,extractKey,equalKey,alloc> hashtable;
Typedef __hashtable_iterator<value,key,hashFun,extractKey,equalKey,alloc> iterator;
Typedef __hashtable_const_iterator<value, key,hashFun,extractkEY, EqualKey,Alloc> const_iterator;
Typedef __hashtable_node<value> node;
//真正的hashtable的迭代器使用的东西
Node* curr; //同一个桶内的节点连结
Hashtable* ht; //跳跃到其他桶时必须知道原来的hashtable的情况
__hashtable_iterator(node* n, hashtable* tab): cur(n), ht(tab){}
__hashtable_iterator(){}
Reference operator*()const{return cur->val;}
Pointer operator->()const{return &(operator*());}
Iterator& operator++(); //指向同一个桶的下一个元素,如果没有则指向下一个桶的元素
{
Const node* old= cur;
Cur = cur->next; //将迭代器的node指针指向下一个即可
If ( !cur ) //如果没有下一个,那就要跳桶
{
Size_type bucket = ht->bkt_num(old->val);
While (!cur && ++bucket < ht->bucket.size())
Cur= ht->buckets[bucket];
//直到找到有node的bucket
}
}
Iterator operator++(int); //后置自增
{
Iterator tmp= *this;
++*this;
Return tmp;
}
Bool operator==(const itetator& it) const{return cur== it.cur;}
};
283 hashtable的数据结构
Template<class value, class Key, class HashFun, class ExtractKey, class EqualKey, class Alloc>
Class hashtable
{
Public:
Typedef HashFun hasher;
Typedef EqualKey key_equal;
Typedef size_t size_type;
Private:
Hasher hash;
Key_equal equals;
ExtractKey get_key;
Typedef __hashtable_node<value> node;
Typdef simple_alloc<node,Alloc> node_allocator;
Vector<node*, Alloc> buckets; //vector的元素类型和配置器,定义bucket队列
Size_type num_elements;
Public:
Size_type bucket_count() const{return buckets.size();}
};
Hashtable 需要传入 (1)节点的value,(2)节点的key ,(3)如果将key进行映射,如果反应时,(4)以及key的比较函数,(5)还有一个空间配置器。
SGI STL使用质数了设计开放链址法的表格大小。
Static const int __stl_num_primes=28;
Static const unsigned long __stl_prime_list[ __stl_num_primes] =
{
28个质数
}
Inline unsigned long __stl_next_prime(unsigned long n)
{
Const unsigned long* first = __stl_prime_list;
Const unsigned long* last = __stl_prime_list+ __stl_num_primes;
Const unsigned long* pos= lower_bound(first,last,n);
Return pos== last? *(last-1):*pos;
}
这28个质数就代表每个阶段(当桶不够时需要进行扩容)bucket的表格的元素大小个数,使用质数有利于更好的防止冲突。
284 hashtable的构造和内存管理
Typedef simple_alloc<node,Alloc> node_allocator; //节点配置器
Node* new_node(const value_type& obj) //仅仅是做初始化操作,并没有涉及到table内的任何结构
{
Node* n= node_allocator::allocate();
n->next=0;
__STL_TRY
{
Construct( &n->val, obj);
Return n;
}
__STL_UNWIND( node_allocator::deallocate(n));
}
Void delete_node(node* n)
{
Destroy( &n->val);
Node_allocator::deallocate(n);
}
Hashtable( size_type n, const HashFun& hf, const EqualKey& eql)
: hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0))
{
Initialize_buckets(n); //初始化bucket,指定hash处理函数
}
Void initialize_buckets(size_type n)
{
Const size_type n_buckets = next_size(n);
Buckets.reserve(n_buckets); //质数空间
Buckets.insert( buckets.end(), n_buckets, (node*)0 );
Num_elements=0;
}
285
很多时候都需要知道某个元素值落在哪个bucket中,这本来是整个hash function的责任,而SGI则将这个责任交给了bkt_num,再让它来调用hash function。
因为有些如果char的元素没有办法直接进行取模,所以要先经过get_key的操作
286 hashtable的复制和删除
要很注意其中的内存释放问题
Template<class V, class K, class HF, class Ex, class Eq, class A>
Void hashtable<V,K,HF,Ex,Eq,A>::clear()
{
For ( size_type I = 0; i<buckets.size(); ++i)
{
Node* curr= buckets[i];
While( curr!=0)
{
Node* next = cur->next;
Delete_node(cur);
Cur=next;
}
Bucket[i]=0;
}
Num_elements=0;
}
Template<class V, class K, class HF, class Ex, class Eq, class A>
Void hashtable<V,K,HF,Ex,Eq,A>::copy_from(const hashtable& ht)
{
Buckets.clear(); //每个新操作都需要清除原来的部分
Buckets.reserve( ht.buckets.size() );
Buckets.insert( buckets.end(), ht.buckets.size() , (node*)0 ); //插入n个为null的元素
__STL_TRY
{
For( size_type I = 0; i< ht.buckets.size(); ++i)
{
If ( const node* cur= ht.buckets[i] ) //如果这个cur不为null
{
Node* copy=new_node(cur->val);
Bucketp[i]=copy;
For( node* next= cur->next; next; cur=next, next = cur->next)
{
Copy->next= new_node( next->val);
Copy = copy->next;
}
}
}
Num_elements= ht.num_elements;
}
__STL_UNWIND( clear() );
}
//逻辑很清楚,首先要清空原有的数据项,然后分配size大小的buckets桶,最后对于每个桶进行桶内元素的深复制。
287 hashTable的使用
#include<hash_set>
//客户端程序不能直接含入<stl_hashtable.h>,应该含入有用到hashtable的容器头文件,例如<hash_set.h>或<hash_map.h>,为什么?
#include<iostream>
Using namespace std;
Int main()
{
Hashtable<int, int, hash<int>, identity<int>, equal_to<int>, alloc> int(50, hash<int>(), equal_to<int>());
Cout<<int.size();
Cout<<int.bucket_count();
Cout<<int.max_bucket_count();
Int.insert_unique(59);
Int.insert_unique(63);
Int.insert_unique(108);
Int.insert_unique(2);
Int.insert_unique(53);
Int.insert_unique(55);
Cout<<int.size();
Hashtable<int, int, hash<int>,identity<int>, equal_to<int>, alloc>::iterator ite= int.begin();
For( int i=0;i<int.bucket_count(); ++i)
{
Int n= int.elems_in_bucket(i);
If(n!=0)
Cout<<i<<n;
}
For (int i=0;i<=47;i++)
Int.insert_equal(i);
Cout<<int.size(); //总共54个节点
Cout<<int.bucket_count(); //超过53个桶,重建(这样可以尽量的保证每个桶内的元素个数平衡)
For( int I =0; i< int.bucket_count(); ++i)
{
Int n= int.elems-in_bucket(i);
If( n!=0)
Cout<<xx;
}
Ite=int.begin();
For(int i=0;i<int.size();++I,++ite)
Cout<<*ite;
Cout<<*(int.find(2);
Cout<<int.count(2);
}
Iterator find(const key_type& key)
{
Size_type n= bkt_num_key( key);
Node* first;
For( first = buckets[n]; first && !equals(get_key(first->val),key); first=first->next){}
Return iterator(first,this);
}
Size_type count( const key_type& key) const
{
Const size_type n = bkt_num_key( key);
Size_type result = 0;
For (const node* cur = bucket[n];cur;cur=cur->next)
If( equals(get_key(cur->val),key)
++result;
Return result;
}
Hashtable中涉及的hash运算,取模等,需要在long等整形实例中才可以应用,SGI STL中使用的是仿函数来进行char的转换,使之可完成类似取模的运算,而如果用户使用的是string类型的桶元素,那么就会出错
Hash table<string,string, hash<string>, identity<string>, equal_to<string>,alloc>
Int ( 50, hash<string>(), equal_to<string>() );
Int.insert_unique( string(“jjhow”);
288 hashSet和set的区别
Hash set底层使用的是hashtable,所以它并没有自动排序的功能,而且它可以利用hashtable的来快速的搜寻到元素,而set的话是使用rb-tree,也可以快速查找元素,当然set里面只保留了value,所以hash set也如此
Template<class Value, class HashFun=hash<Value>, class EqualKey= equal_to<Value>,class Alloc=alloc>
Class hash_set
{
Private:
Typedef hashtable<Value,Value,HashFun,identity<Value>, EqualKey,Alloc> ht;
Ht rep; //底层借用hash table来完成
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() ) {}
Explicit hash_set( size_type n) : rep (n, hasher(), key_equal()){}
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){}
Template< class InputIterator>
Hash_set ( InputIterator f , InputIterator l)
: rep( 100, hasher(), key_equal()) {rep.insert_unique(f,l);}
Template<class InputIterator>
Hash_set( InputIterator f, InputIterator l, size_type n)
: rep ( n, hasher(), key_equal() )
{ rep.insert_unique(f,l);}
Template<class InputIterator>
Hash_set( InputIterator f, inputIterator l, size_type n, const hasher& hf)
: rep( n,hf,key_equal()){rep.insert_unique(f,l);}
Template<class InputIterator>
Hash_set(InputIterator f, InputIterator l, size_type n, const hasher& hf, const key_equal& eql): rep( n,hf,eal){rep.insert_unique(f,l);}
Public:
Size_type size() const{return rep.size();}
Size_type max_size()
Bool empty() const
Void swap ( Hash_set& hs)
Friend bool operator==
Iterator begin() const
Iterator end() const
Public-
Pair< iterator, bool> insert( const value_type & obj)
{
Pair<typename ht::iterator, bool> p = rep.insert_unique( obj):
//底层的hashtable的插入,散列
Return pait<iterator, bool>(p.first, p.second); //构建pair
}
Template<class InputIterator>
Void insert(InputIterator f, InputIterator l)
{
Rep.insert_unique(f,l):
}
Pair<iterator, bool>insert_norssize(const value_type& obj)
{
Pair<typename ht::iterator, bool> p = rep.insert_unique_noresize(obj);
Return pair<iterator, bool>(p.first, p.second);
}
Iterator find( const key_type& key) const { return rep.find(key);}
Size_type count( const key_type& key) const {return rep.count(key);}
Pair<iterator, iterator> equal_range( const key_type& key) const
{return rep.equal_range(key);}
Size_type erase( const key_type& key){return rep.erase(key);}
Void erase(iterator it){ rep.erase(it);}
Void clear()
Public:
Void resize(size_type hint)
Size_type bucket_count() const
Size_type max_bucket_count() const
Size_type elems_in_bucket(size_type n) const;
};
Hash_set的使用
#include<iostream>
#include<hash_set>
#include<cstring>
Using namespace std;
Struct eqstr
{
Bool operator() (const char* s1, const char* s2) const
{
Return strcmp( s1,s2)==0;
}
};
//从char型hash set中找到word char
Void lookup(const hash_set<const char*, hash<const char*>, eqstr>& Set, const char* word)
{
Hash_set<const char*, hash<const char*>, eqstr>:: const_iterator it = set.find(word);
Cout<<” “<<word<<”:” <<( it!=Set.end()? “present”:”not present”;
}
Int main()
{
Hash_set<const char*, hash<const char*>, eqstr> set;
Set.insert( “dd”);
Lookup( set,“dd“);
Hash_set<const char*, hash<const char*>, eqstr>::iterator ite1=set.begin();
//这个hashset使用的比较函数时自定义的比较函数,因为其类型的特殊性
Hash_set<const char*, hash<const char*>,eqstr>::iterator ite2 = set.end();
For( ;iter1!=iter2;++iter1)
Cout<<*iter1<<’’;
}
289 hash_map
同样也是以hashtable为底层,hash map可以利用key快速定位到value,和普通的以rb tree为底层的map一致,而注意的是hashmap也没有排序功能、
源码和hash_set基本一致
#include<iostream>
#include<hash_map>
#include<cstring>
Using namespace std;
Struct eqstr
{
Bool operator() (const char*s2, const char* s2) const
{ return strcmp(s1,s2)==0;}
};
Int main()
{
Hash_map<const char*, int, hash<const char*>,eqstr> days;
Days[“key”]=value; //key是char,value是int
Cout<<days[“key”];
Hash_map<const char*,int,hash<const char*>,eqstr>::iterator ite1=days.begin();
Hash_map<const char*, int, hash<const char*>, eqstr>::iterator ite2=days.end();
For(;ite1!=ite2;++ite1)
Cout<< ite1->first<<’’;
}
290
Hash_multiset的特性和multiset完全相同,只是底层使用的是hashtable,而且插入的时候可以重复,所以使用insert_equal(),它的使用方式和hash_set 一致
291
Hash_multimap
Hash_multimap的特性和multimap完全相同,只是底层使用的话hashtable,并且插入使用的是inset_equal
作者:Aga.J
出处:http://www.cnblogs.com/aga-j
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
个人学习笔记仅供本人记录知识所用,不属发表性文章。