hashtable

hashtable

hash table可提供对任何有名项(named item)的存取操作和删除操作。由于操作对象是有名项,所以,hash table也可以被视为一种字典结构。这种结构的用意在于提供常数时间的基本操作。

线性探测

当hash function 计算出某个元素的插入位置,而该位置上的空间已不再可用时,最简单的方法就是循序往下一一寻找(如果到达尾端,就绕道头部继续寻找),直到找到一个可用空间为止。只要表格足够大,总是能够找到一个安身立命的空间。但是要花多少时间就很难说了。进行元素搜寻操作时,如果 hash function 计算出来的位置上的元素值与我们的搜寻目标不符,就循序往下一一寻找,直到找到吻合者,或直到遇上空格元素。至于元素的删除,必须采用惰性删除,也就是只标记删除记号,实际删除操作待表格重新整理时再进行——这是因为hash table中的每一个元素不仅表述它自己,也关系到其他元素的排列。
在这里插入图片描述

二次探测

在这里插入图片描述

开链

开链法就是在每一个表格元素中维护一个listhash function为我们分配某一个list,然后我们在那个list身上执行元素的插入,搜寻,删除等操作。虽然针对list而进行的搜寻只能是一种线性操作,但如果list够短,速度还是够快。

在这里插入图片描述

template<class Value>
struct __hashtable_node
{
	__hashtable_node* next;
    Value val;
};

在这里插入图片描述
注意,bucket所维护的linked list,并不采用STL的list或slist,而是自行维护上述的hash table node。至于buckets聚合体,则是以vector完成,以便有动态扩充能力。


hashtable的迭代器

template <class Value, class Key, class HashFcn, class ExtractKey,
				class EqualKey, class Alloc>
struct __hashtable_iterator{
	typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>;
    typedef __hashtable_iterator<Value,Key,Hashfcn,ExtractKey,EqualKey,Alloc>
        iterator;
    typedef __hashtable_const_iterator<Value,Key,Hashfcn,
    		ExtractKey,EqualKey,Alloc> const_iterator;
    typedef __hashtable_node<Value> node;
    
    typedef forward_iterator_tag iterator_category;
    typedef Value value_type;
    typedef ptrdiff_t difference_type;
    typedef size_t size_type;
    typedef Value& reference;
    typedef Value* pointer;
    
    node* cur;			//迭代器目前所指节点
    hashtable* ht;		//保持对容器的连结关系(可能需要从bucket跳到bucket)
    
    __hashtable_iterator(node* n,hashtable* tab) : cur(n),ht(tab){}
    __hastable_iterator(){}
    reference operator*() const { return cur->val; }
    pointer operator->() const { return &(operator*()); }
    iterator& operator++(){
        const node* old = cur;
        cur = cur->next;
        if(!cur){
            //根据元素值,定位出下一个bucket。其起头处就是我们的目的地
            size_type bucket = ht->bkt_num(old->val);
            while(!cur && ++bucket < ht->buckets.size())
                cur = ht->buckets[bucket];
        }
        return *this;
    }
    iterator operator++(int){
        iterator tmp = *this;
        ++*this;
        return tmp;
    }
   	
    bool operator==(const iterator& it) const { return cur == it.cur; }
    bool operator!=(const iterator& it) const { return cur != it.cur; }
};

请注意,hashtable的迭代器没有后退操作(operator--()),hashtable也没有定义所谓的逆向迭代器。

hashtable的数据结构

template <class Value, class Key, class HashFcn, class ExtractKey,
				class EqualKey, class Alloc>
class hashtable{
public:
    typedef HashFcn hasher;
    typedef EqualKey key_equal;
    typedef size_t size_type;

private:
	hasher hash;
    key_equal equals;
    ExtractKey get_key;
    
    typedef __hashtable_node<Value> node;
    typedef simple_alloc<node, Alloc> node_allocator;
    
    vector<node*, Alloc> buckets;
    size_type num_elements;

public:
    size_type bucket_count() const { return buckets.size(); }
}

hashtable的模板参数相当多,包括:

  • Value:节点的实值类型
  • Key:节点的键值类型
  • HashFcn:hash function的函数类型
  • ExtractKey:从节点中取出键值的方法
  • EqualKey:判断键值相同与否的方法
  • Alloc:空间配置器

注意,先前谈及概念时,指出hash functions是计算元素位置的函数,SGI将这项任务赋予了bkt_num(),由它调用hash function取得一个可以执行取模运算的值。

虽然开链法并不要求表格大小必须为质数,但是SGI STL仍然以质数来设计表格大小,并且将28个质数计算好,以便随时访问。

在这里插入图片描述

hashtable的构造与内存管理

typedef simple_alloc<node, Alloc> node_allocator;

下面是节点配置函数与节点释放函数

node* new_node(const value_type& obj)
{
    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)
{
    destory(&n->val);
    node_allocator::deallcate(n);
}
hashtable(size_type n,
         const HashFcn& hf,
         const EqualKey& eql)
    : hash(hf), equals(eql),get_key(ExtractKey()), num_elements(0)
{
    initialze_buckets(n);    
}
void initialize_buckets(size_type n)
{
    const size_type n_buckets = nuext_size(n);
    //举例,传入50,返回53,以下首先保留53个元素空间,然后将其全部填0
    buckets.reserve(n_buckets);
    buckets.insert(buckets.end(), n_buckets, (node*)0);
    num_elements = 0;
}
//next_size()返回最接近n并大于n的质数
size_type next_size(size_type n) const { return __stl_next_prime(n); }

插入操作与表格重整

当客户端开始插入元素(节点)时

iht.insert_unique(59);
iht.insert_unique(63);
iht.insert_unique(108);

//插入元素,不允许重复
pair<iterator, bool> insert_unique(const value_type& obj)
{
    resize(num_elements + 1);		//判断是否需要重建表格,如需要就扩充
    return insert_unique_noresize(obj);
}
//以下判断是否需要重建表格。如果不需要,立刻返回。如果需要,就动手
tempate<class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V,K,HF,Ex,Eq,A>::resize(size_type num_elements_hint)
{
    //以下“表格重建与否”的判断原则颇为奇特,是拿元素个数(把新增元素计入后)和
    //bucket vector的大小来比,如果前者大于后者,就重建表格
    //由此判断,链表的节点数和buckets vector的大小相同
    const size_type old_n = buckets.size();
    if(num_elemets_hint > old_n){		//确定是否真的需要重新配置
        const size_type n = next_size(num_element_hint);	//找出下一个质数
        if(n > old_n){
            vector<node*, A> tmp(n, (node*)0);
            __STL_TRY{
                //以下处理每一个旧的bucket
                for(size_type bucket = 0; bucket < old_n; ++bucket){
                    node* first = buckets[bucket];		//指向节点所对应的串行的起始节点
                    //以下处理每一个旧bucket所含的每一个节点
                    while(first){
                        size_type new_bucket = bkt_num(first->val, n);	//找出节点落在哪一个新bucket内
                        buckets[bucket] = first->next;	//令旧bucket指向其所对应串行的下一个节点
                        //将当前节点插入到新bucket内,称为其对应串行的第一个节点
                        first->next = tmp[new_bucket];
                        tmp[new_bucket] = first;
                    	//回到旧bucket所指的待处理串行,准备处理下一个节点
                        first = buckets[bucket];
                    }
                }
                buckets.swap(tmp);	//vector::swap。新旧两个buckets对调
                					//离开时释放local tmp的内存
            }
        }
    }
}
//在不需要重建表格的情况下插入新节点。键值不允许重复
tempate<class V, class K, class HF, class Ex, class Eq, class A>
pair<typename hashtable<V,K,HF,Ex,Eq,A>::iterator, bool>
hashtable<V,K,HF,Ex,Eq,A>::insert_unique_noresize(const value_type& obj)
{
	const size_type n = bkt_num(obj);	//决定obj应位于#n bucket
    node* first = buckets[n];
    for(node* cur = first; cur; cur = cur->next)
        if(equals(get_key(cur->val), gey_key(obj)))
            //如果发现与链表中的某个键值相同,就不插入,立刻返回
            return pair<iterator, bool>(iterator(cur, this), false);
	node* tmp = new_node(obj);	//产生新节点
    tmp->next = first;
    buckets[n] = tmp;	//令新节点称为链表的第一个节点
    ++num_elements;	//节点个数加1
    return pair<iterator, bool>(iterator(tmp, this), true);
}

如果客户端执行的是另一种节点插入行为(不再是insert_unique,而是insert_equal):

iterator insert_equal(const value_type& obj)
{
    resize(num_elements + 1);		//判断是否需要重建表格,如需要就扩充
    return insert_equal_noresize(obj);
}
//在不需要重建表格的时候插入新节点,键值允许重复
tempate<class V, class K, class HF, class Ex, class Eq, class A>
typename hashtable<V,K,HF,Ex,Eq,A>::iterator
hashtable<V,K,HF,Ex,Eq,A>::insert_unique_noresize(const value_type& obj)
{
    const size_type n = bkt_num(obj);
    node* first = buckets[n];	//令first指向bucket对应之链表头部
    
    for(node* cur = first;cur;cur=cur->next)
        if(equals(get_key(cur->val), get_key(obj))){
            //如果发现与链表中的某键值相同,就马上插入,然后返回
            node* tmp = new_node(obj);	//产生新节点
            tmp->next = cur->next;		//将新节点插入目前位置之后
            cur->next = tmp;
            ++num_elements;
            return iterator(tmp, this);		//返回迭代器,指向新节点
        }
    //没有发现重复的键值
    node* tmp = new_node(obj);		//产生新节点
    tmp->next = first;				//将新节点插入链表头部
    buckets[n] = tmp;
    ++num_elements;					//节点个数累加1
    return iterator(tmp, this);		//返回一个迭代器,指向新增节点
}

判断元素的落脚处

hash function需要计算出某个元素落脚于哪一个bucket之内,SGI把这个任务包装了一层,先交给bkt_num()函数,然后再由此函数调用hash function,取得一个可以执行modulus(取模)运算的数值。为什么这么做?因为有些元素的类型无法直接拿来对hashtable大小进行模运算。

//版本1:接受实值value和buckets个数
size_type bkt_num(const value_type& obj, size_t n) const {
    return bkt_num_key(get_key(obj), n);
}
//版本2:只接受实值value
size_type bkt_num(const value_type& obj) const{
    return bkt_num_key(get_key(obj));
}
//版本3:只接受键值
size_type bkt_num_key(const key_type& key) const{
    return bkt_num_key(key, buckets.size());
}
//版本4:接受键值和buckets个数
size_type bkt_num_key(const key_type& key, size_t n) const
{
	return hash(key) % n;
}

复制和整体删除

tempate<class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V,K,HF,Ex,Eq,A>::clear()
{
    //针对每一个bucket
    for(size_type i = 0; i < buckets.size(); ++i){
        node* cur = buckets[i];
        //将bucket list中的每一个节点删除掉
        while(cur != 0){
            node* next = cur->next;
            delete_node(cur);
            cur = next;
        }
        buckets[i] = 0;
    }
    num_elements = 0;
    //注意,buckets vector并未释放掉空间,仍保有原来大小
}
tempate<class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V,K,HF,Ex,Eq,A>::copy_fron(const hashtable& ht)
{
    //清除己方的buckets vector。这操作是调用vector::clear,将整个容器清空
    buckets.clear();
    //为己方的buckets vector保留空间,是与对方相同
    //如果己方空间大方对方,就不动,如果己方空间小于对方,就会增加
    buckets.reserve(ht.buckets.size());
    //从己方的buckets vector尾端,插入n个元素,其值为null指针
    //注意,此时buckets vector为空,所以所谓尾端,就是起头处
    buckets.insert(buckets.end(), ht.buckets.size(), (node*)0);
    __STL_TRY{
        for(size_type i = 0; i < ht.buckets.size(); i++){
            if(const node* cur = ht.buckets[i]){
                node* copy = new_node(cur->val);
                buckets[i] = copy;
                
                for(node* next = cur->next;next;cur=next,next=cur->next){
                    copy->next = new_node(next->val);
                    copy = copy->next;
                }
            }
        }
        num_elements = jt.num_elements;
    }
    __STL_UNWIND(clear());
}

hash function

SGI hashtable无法处理string,double,float。欲处理这些类型,用户必须自行定义hash function

可以处理char,unsigned char,signed char, short,int,unsigned int, long, unsigned long, char*, const char*

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值