代码改变世界

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;

 clip_image002

//真正的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();}

};

 clip_image004

 clip_image006

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。

 clip_image008

 clip_image010

 clip_image012

 clip_image014

因为有些如果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来完成

 clip_image016

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