1. 程式人生 > >STL原始碼分析之hashtable關聯容器 上

STL原始碼分析之hashtable關聯容器 上

前言

前面我們分析過RB-tree關聯容器, RB-tree在插入(可重複和不可重複), 刪除等操作時間複雜度都是O(nlngn), 以及滿足5個規則, 以及以他為底層的配接器; 本節就來分析hashtable另個關聯容器, 他在插入, 刪除等操作都可以做到O(1)的時間複雜度.

雜湊表概念

雜湊方法

  1. 直接定址法:取關鍵字或關鍵字的某個線性函式值為雜湊地址。(這種雜湊函式叫做自身函式)
  2. 數字分析法:假設關鍵字是以r為基的數,並且雜湊表中可能出現的關鍵字都是事先知道的,則可取關鍵字的若干數位組成雜湊地址。
  3. 平方取中法:取關鍵字平方後的中間幾位為雜湊地址。通常在選定雜湊函式時不一定能知道關鍵字的全部情況,取其中的哪幾位也不一定合適,而一個數平方後的中間幾位數和數的每一位都相關,由此使隨機分佈的關鍵字得到的雜湊地址也是隨機的。取的位數由表長決定。
  4. 摺疊法:將關鍵字分割成位數相同的幾部分(最後一部分的位數可以不同),然後取這幾部分的疊加和(捨去進位)作為雜湊地址。
  5. 隨機數法
    6.除留餘數法:取關鍵字被某個不大於散列表表長m的數p除後所得的餘數為雜湊地址。不僅可以對關鍵字直接取模,也可在摺疊法、平方取中法等運算之後取模。對p的選擇很重要,一般取素數或m,若p選擇不好,容易產生衝突。

hashtable解決衝突的辦法就是開鏈

衝突處理

雜湊表的衝突處理也有很多種.

  1. 開放定址法
    • 線性探測 : 本來的位置被佔有(衝突), 重新再往後找到第一個有空的位置插入進去
    • 二次探測 : 本來的位置被佔有(衝突), 每次有衝突就平方一次重新查詢
  2. 開鏈 : 本來的位置被佔有(衝突), 形成一個連結串列插入到連結串列中

裝載因子 : 裝入表中的元素 / 表的實際大小. 裝載因子越大說明衝突的可能性就越大.

hashtable分析

桶與節點.

桶 : 定義的雜湊表大小, 以vector為桶

節點 : 連結串列

// 這裡連結串列是自定義的, 並沒有採用list和slist
template <class Value>
struct __hashtable_node
{
  __hashtable_node* next;
  Value val;
}; 
// 前置宣告
template <
class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc = alloc> class hashtable; template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc> struct __hashtable_iterator; template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc> struct __hashtable_const_iterator;

hashtable迭代器

hashtable迭代器是forward_iterator_tag型別, 正向迭代器, 所以他也就沒有過載-- , 沒有回退.

__hashtable_const_iterator__hashtable_iterator一樣, 這裡就只分析後者

template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
  typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>  hashtable;
  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;	// 定義雜湊表指標

  __hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {}
  __hashtable_iterator() {}
  // 過載指標
  reference operator*() const { return cur->val; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
	// 重在++, 因為是正向迭代器, 所以沒有--
  iterator& operator++();
  iterator operator++(int);
  bool operator==(const iterator& it) const { return cur == it.cur; }
  bool operator!=(const iterator& it) const { return cur != it.cur; }
};

定義雜湊表大小

定義了雜湊表的大小, 預設long為32位, 定義了28個數組大小. 雜湊表的的大小都是素數, 減少衝突

// 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
};
// 找到大於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;
  const unsigned long* pos = lower_bound(first, last, n);
  return pos == last ? *(last - 1) : *pos;
}

雜湊表

hashtable型別定義

模板引數含義 :

  1. Value : 節點的實值型別
  2. Key : 節點的鍵值型別
  3. HashFcn : hash function的型別
  4. ExtractKey : 從節點中取出鍵值的方法(函式或仿函式)
  5. EqualKey : 判斷鍵值是否相同的方法(函式或仿函式)
template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
class hashtable {
public:
  typedef Key key_type;
  typedef Value value_type;
  typedef HashFcn hasher;
  typedef EqualKey key_equal;

  typedef size_t            size_type;
  typedef ptrdiff_t         difference_type;
  typedef value_type*       pointer;
  typedef const value_type* const_pointer;
  typedef value_type&       reference;
  typedef const value_type& const_reference;

	// 這裡返回的都是仿函式
  hasher hash_funct() const { return hash; }
  key_equal key_eq() const { return equals; }

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;	// 以vector作為桶, node*
  size_type num_elements;		// 雜湊表中元素個數的計數

public:
  typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> iterator;

  typedef __hashtable_const_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> const_iterator;

// 迭代器定義為友元
  friend struct
  __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>;
  friend struct
  __hashtable_const_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>;
  ...
};

構造與解構函式

template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
class hashtable {
	...
public:
	// 建構函式, 沒有定義預設建構函式
  hashtable(size_type n, const HashFcn&  hf,const EqualKey&   eql,const ExtractKey& ext)
    : hash(hf), equals(eql), get_key(ext), num_elements(0)
  {
    initialize_buckets(n);
  }

  hashtable(size_type n, const HashFcn&  hf, const EqualKey&   eql)
    : hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0)
  {
    initialize_buckets(n);
  }
	// 拷貝建構函式
  hashtable(const hashtable& ht)
    : hash(ht.hash), equals(ht.equals), get_key(ht.get_key), num_elements(0)
  {
    copy_from(ht);
  }
  // 解構函式
  ~hashtable() { clear(); }
  ...
};

基本屬性獲取

template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
class hashtable {
	...
public:
  size_type size() const { return num_elements; }
  size_type max_size() const { return size_type(-1); }
  bool empty() const { return size() == 0; }

	// 交換, 並不是交換所有資料, 只是交換了其指標指向和個數
  void swap(hashtable& ht)
  {
    __STD::swap(hash, ht.hash);
    __STD::swap(equals, ht.equals);
    __STD::swap(get_key, ht.get_key);
    buckets.swap(ht.buckets);
    __STD::swap(num_elements, ht.num_elements);
  }

  iterator begin()
  { 
    for (size_type n = 0; n < buckets.size(); ++n)
    	// 從頭遍歷桶, 如果有不空的連結串列存在, 就返回該連結串列的第一個元素
      if (buckets[n])
        return iterator(buckets[n], this);
    // 沒有元素就返回end.
    return end();
  }
	// end返回0
  iterator end() { return iterator(0, this); }

  const_iterator begin() const
  {
    for (size_type n = 0; n < buckets.size(); ++n)
      if (buckets[n])
        return const_iterator(buckets[n], this);
    return end();
  }
  const_iterator end() const { return const_iterator(0, this); }
  
  // 返回桶的大小
  size_type bucket_count() const { return buckets.size(); }

  size_type max_bucket_count() const
    { return __stl_prime_list[__stl_num_primes - 1]; } 

 // 返回指定位置的節點的個數
  size_type elems_in_bucket(size_type bucket) const
  {
    size_type result = 0;
    for (node* cur = buckets[bucket]; cur; cur = cur->next)
      result += 1;
    return result;
  }
  ...
};

總結

本節只是分析了雜湊表的基本構成是桶(vector), 連結串列(解決衝突). hashtableforward_iterator_tag型別的正向迭代器,沒有--操作, 下節我們繼續分析剩下的程式碼.