STL源碼剖析hashtable

hashtable概述

      在前面介紹的RB-tree中,可以看出紅黑樹的插入、查找、刪除的平均時間複雜度爲O(nlogn)。但這是基於一個假設:輸入數據具有隨機性。而哈希表/散列表hash table在插入、刪除、查找上具有“平均常數時間複雜度”O(1);這種表現是以統計爲基礎,且不依賴輸入數據的隨機性。

      hashtable的實現主要要通過幾種方式:線性探測(linear probing),二次探測(quadratic probing),開鏈(separate chaining)......

      在SGI STL中,就是使用開鏈這種方法。hash table表格內的每個元素爲一個桶子bucket。這裏bucket所維護的linked list不是

STL得list或slist,而是下面的hashtable node所形成的list。而buckets的聚合體使用vector來實現。

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

使用注意

1.在使用hashtable的時候,不能直接調用<stl_hashtable.h>,應該含乳有用到hashtable的容器頭文件,例如:<hash_set.h>和<hash_map.h>。

#include<hash_set>
#include<hash_map>

2.hash function只能處理int,short,long,char和char*。

不能處理string,double和float類型,這三個類型需要用戶自定義hash function.

3.鍵值相同的元素,一定落在同一個bucket list中,鍵值不同的元素,有可能落在同一個bucket list中。

插入操作和表格重整

當對hashtable進行插入操作的時候,會判斷需要重整表格,也就是buckets這個vector。

書中講到:“表格重建與否”的判斷原則頗爲奇特,是那元素個數(把新增元素計入後)和buckets這個vector的大小來比較。如果前

者大於後者,就重建表格。由此可判知,每個bucket(list)的最大容量和buckets vector的大小相同。

這裏可以理解爲,最糟糕的情況下,每個元素都在第一個bucket中,那麼該list的容量最大與buckets的大小相同。在最好的情況

下,是每個bucket中都只有一個元素,從而可以使得查找元素的速度很快。但是在常規情況下,肯定會有某些bucket中的元素不

止一個,從而使得有的bucket中爲空。

不允許重複插入

pair<iterator,bool>insert_unique(const value_type& obj)
{
    resize(num_elements+1);//在該函數中判斷是否需要重整表格,若需要就進行重整
    return insert_unique_noresize(obj);
}

表格重整

template <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)
{
  const size_type old_n = buckets.size();//bucket vector 的大小
  /*如果元素個數(把新增元素計入後)比bucket vector 大,則需要重建表格*/
  if (num_elements_hint > old_n) {
      const size_type n = next_size(num_elements_hint);//找出下一個質數
		
      if (n > old_n) { //old_n不是質數表裏面的最大值時,纔可擴展
        vector<node*, A> tmp(n, (node*)0);//設立新的bucket vector,大小爲n
	    //以下處理每一箇舊的bucket
        for (size_type bucket = 0; bucket < old_n; ++bucket) {
            node* first = buckets[bucket];//指向節點所對應之串行(鏈表)的起始節點
            while (first) {//處理單個bucket中的鏈表
                size_type new_bucket = bkt_num(first->val, n);//找出節點落在哪一個新的bucket內
                buckets[bucket] = first->next;//令舊bucket指向其所對應的鏈表的下一個節點,以便迭代處理
                /*下面將當前節點插入到新的bucket內,成爲其對應鏈表的第一個節點,這裏的實現比較巧妙
                相當於插入新節點到新bucket vector中,新插入的元素插入到鏈表的首位置,這裏不同於一般的插入的是,
                由於之前已有元素佔據空間,這裏只是修改節點指針指向*/
                first->next = tmp[new_bucket];
                tmp[new_bucket] = first;
                first = buckets[bucket];//回到舊bucket所指的待處理鏈表,準備處理下一個節點
            }
        }
        buckets.swap(tmp);//vector::swap 新舊兩個buckets 對調(淺修改)
        /*對調兩方如果大小不同,大的會變小,小的會變大,離開時釋放local tmp 的內存*/
     }
  }
}

注意:每次調整的時候,不是擴大兩倍,是以質數來設定表格大小。

 
/*質數表*/
// Note: assumes long is at least 32 bits.
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
	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, 3221225473, 4294967291
};
 
/*以下找出上述28個質數之中,最接近並大於 n的那個質數(有的話),沒有取最大*/
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;//尾的下一位置
	/*泛型算法,返回一個迭代器,指向第一個不小於 n的元素*/
	const unsigned long* pos = lower_bound(first, last, n);
	return pos == last ? *(last - 1) : *pos;//如果沒有比它大的就取最大的
}

size_type next_size (size_type n) const {return __stl_next_prime(n);}

不允許重複插入,不需要重建表格

/*插入元素,不允許重複*/
template <class V, class K, class HF, class Ex, class Eq, class A>
pair<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);//定位bucket
	node* first = buckets[n];
 
	/*判斷插入元素是否有重複*/
	for (node* cur = first; cur; cur = cur->next)
	if (equals(get_key(cur->val), get_key(obj)))
		return pair<iterator, bool>(iterator(cur, this), false);
 
	node* tmp = new_node(obj);//產生新節點 node_allocator::allocate()
	/*先插入節點放在鏈表最前面*/
	tmp->next = first;
	buckets[n] = tmp;
	++num_elements;//元素個數增加
	return pair<iterator, bool>(iterator(tmp, this), true);
}

允許重複插入

pair<iterator,bool>insert_equal(const value_type& obj)
{
    resize(num_elements+1);//在該函數中判斷是否需要重整表格,若需要就進行重整
    return insert_equal_noresize(obj);
}

不需要重建的情況下,允許重複插入

/*插入元素,允許重複*/
template <class V, class K, class HF, class Ex, class Eq, class A>
hashtable<V, K, HF, Ex, Eq, A>::iterator
hashtable<V, K, HF, Ex, Eq, A>::insert_equal_noresize(const value_type& obj)
{
	const size_type n = bkt_num(obj);//定位bucket
	node* first = buckets[n];//鏈表頭節點
 
	for (node* cur = first; cur; cur = cur->next)
	if (equals(get_key(cur->val), get_key(obj))) {//如果插入元素是重複的(與cur->val重複)
		node* tmp = new_node(obj);
		tmp->next = cur->next;//新增元素插入重複元素的後面
		cur->next = tmp;
		++num_elements;
		return iterator(tmp, this);
	}
	//沒有重複,等同於insert_unique_noresize()
	node* tmp = new_node(obj);
	tmp->next = first;
	buckets[n] = tmp;
	++num_elements;
	return iterator(tmp, this);
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章