《数据结构与算法分析:C语言描述》复习——第七章“哈希”——哈希表

2014.06.22 12:36

简介:

  哈希是一种数学思想,将不定长数据通过函数转换为定长数据。不定长数据通常意味着碎片化,动态分配内存等等影响存储和性能的因素。当这个定长数据是一个无符号整数时,可以用来表示数组的下标。因此就可以通过这样的哈希算法来把自定义类型的数据存入一个数组中。这样就有了哈希表的基本思想:把自定义类型(定长或者不定长)的数据存入一个数组中,具体应该插入的位置,则由哈希函数计算得出。

描述:

  举个例子,有一个string类型的数组长度为5。那么我们将一些字符串插入到这个数组的过程中。采用如下哈希函数:

    size_t hasher(const string &s)

    {

      size_t result = 0;

      for (int i = 0; i < s.length(); ++i) {

        result = (result * 10 + s[i]) % 5;

      }

 

      return result;

    }

  以上的hasher函数是一个从string类型到size_t类型的映射。通过观察代码不难发现,函数的返回值始终介于[0, 5),也就可以作为数组下标来使用。

  如果我们姑且认为string是一个整体的话,那么只需要花费“O(1)”的时间就能计算出哈希值,并以这个哈希值(对数组长度取模)来作为插入的位置。

  

  显然,这个哈希函数并不能保证两个不同的字符串是否会得出同一个整数值。那么当两个字符串有相同的哈希值时,后来者就只能另找位置插入,这种情况称为“冲突”。

  另找位置的办法大致分两种:

    1. 开放寻址

      如果要插入的位置已经被占据了,那么我们使用试探函数进行至少1次试探。一个简单的线性试探函数可以是下面这样:

        size_t linearProbing(const int i)

        {

          return i;

        }

        

        size_t quadraticProbing(const int i)

        {

          return i * i;

        }

      在执行第i次试探时,我们将原始的哈希值hash_val加上试探值probing(i),得到hash_value' = hash_value + probing(i),如果这个哈希值能够对应到一个空闲位置,则插入成功。否则继续下一次试探。关于quadraticProbing,可能存在一个隐患,你看出来了吗?

    2. 拉链法

      所谓拉链,其实就是把数组的每个位置用一条链表来表示,这样所有具有相同哈希值的元素都会穿在一条链表上。这样一来,各种操作的性能都会相应受到链表本身的影响而降低。这种方法的插入操作总是分为两部分:先确定插入到数组的哪个下标,然后插入到对应的那条链表中。

  

  对于开放寻址的哈希表,每个数据元素占据一个数组位置。这样使用一段时间之后,哈希表可能会变得很“满”,导致冲突的发生频率升高,降低哈希表的效率。此时我们用“负载系数”来衡量的程度。定义负载系数load_factor = used_slots / total_slots。当负载系数超过某个阈值时(比如0.5),我们就把数组空间扩大,然后把其中所有元素重新插入一次,这个过程称为rehash。如果你熟悉vector动态扩大的过程,想象这个应该很容易。

 

  关于unordered_set和unordered_map,是boost中很实用的工具类,可以认为就是哈希表。现在它俩已经是C++11中的STL工具类了。虽然和map与set的用法极其类似,但两者的数据结构不同,因此原理和复杂度都不同。通过unordered,你也应该明白哈希表不保证插入元素的顺序,而map和set所基于的平衡树则保证元素插入后保持有序。

  哈希表的关键是键值key。因此从unordered_set<key>到unordered_map<key, value>所需要的改动其实非常小,仅仅是对于value域的一些操作而已。对于哈希表的性质和结构则完全没有影响。

实现:

我实现的一个HashSet例子,使用开放寻址:

  1 // My implementation for hash set.

  2 #include <iostream>

  3 #include <string>

  4 #include <vector>

  5 using namespace std;

  6 

  7 template <class KeyType>

  8 struct HashFunctor {

  9     size_t operator () (const KeyType &key) {

 10         const char *ptr = (const char *)&key;

 11         size_t size = sizeof(key);

 12         size_t result;

 13         

 14         result = 0;

 15         for (size_t i = 0; i < size; ++i) {

 16             result = (result << 1) ^ *(ptr + i);

 17         }

 18         

 19         return result;

 20     }

 21 };

 22 

 23 template<>

 24 struct HashFunctor<string> {

 25     size_t operator() (const string &key) {

 26         size_t size = key.length();

 27         size_t result;

 28         

 29         result = 0;

 30         for (size_t i = 0; i < size; ++i) {

 31             result = (result << 1) ^ key[i];

 32         }

 33         

 34         return result;

 35     }

 36 };

 37 

 38 template <class KeyType>

 39 class HashSet {

 40 public:

 41     HashSet() {

 42         m_size = 0;

 43         m_capacity = MIN_BUCKET_NUM;

 44         m_data.resize(m_capacity);

 45         m_occupied.resize(m_capacity);

 46         

 47         for (size_t i = 0; i < m_capacity; ++i) {

 48             m_occupied[i] = false;

 49         }

 50     }

 51     

 52     void insert(const KeyType& key) {

 53         size_t h = _findKey(key);

 54         

 55         if (m_occupied[h]) {

 56             // value already inserted

 57             return;

 58         }

 59         

 60         m_data[h] = key;

 61         m_occupied[h] = true;

 62         ++m_size;

 63         

 64         if (load_factor() >= 0.5) {

 65             _rehash(m_capacity * 2 + 1);

 66         }

 67     }

 68     

 69     void remove(const KeyType& key) {

 70         size_t h = _findKey(key);

 71         

 72         if (!m_occupied[h]) {

 73             // value not found

 74             return;

 75         }

 76         

 77         m_occupied[h] = false;

 78         --m_size;

 79         

 80         if (m_capacity > MIN_BUCKET_NUM && load_factor() <= 0.05) {

 81             _rehash(m_capacity / 2);

 82         }

 83     }

 84     

 85     void update(const KeyType& old_key, const KeyType& new_key) {

 86         remove(old_key);

 87         insert(new_key);

 88     }

 89     

 90     bool find(const KeyType& key) {

 91         size_t h = _findKey(key);

 92         

 93         return m_occupied[h];

 94     }

 95     

 96     size_t size() {

 97         return m_size;

 98     }

 99     

100     void clear() {

101         m_size = 0;

102         for (size_t i = 0; i < m_capacity; ++i) {

103             m_occupied[i] = false;

104         }

105     }

106     

107     double load_factor() {

108         return (double)m_size / (double)m_capacity;

109     }

110     

111     ~HashSet() {

112         m_data.clear();

113         m_occupied.clear();

114     }

115 private:

116     static const size_t MIN_BUCKET_NUM = 5;

117     size_t m_size;

118     size_t m_capacity;

119     vector<KeyType> m_data;

120     vector<bool> m_occupied;

121     HashFunctor<KeyType> m_hasher;

122     

123     size_t _findKey(const KeyType& key) {

124         size_t hash_value = m_hasher(key);

125         size_t h;

126         size_t i;

127         

128         i = 0;

129         while (i < m_capacity) {

130             // only works for linear probing

131             // if applied to quadratic probing, the number of buckets must be carefully chosen.

132             h = (hash_value + _probeFunction(i)) % m_capacity;

133             if (!m_occupied[h] || key == m_data[h]) {

134                 return h;

135             } else {

136                 ++i;

137             }

138         }

139         

140         return m_capacity;

141     }

142 

143     size_t _probeFunction(int i) {

144         return i;

145     }

146     

147     void _rehash(size_t new_capacity) {

148         vector<KeyType> old_data;

149         vector<bool> old_occupied;

150         

151         old_data = m_data;

152         old_occupied = m_occupied;

153         

154         m_data.resize(new_capacity);

155         m_occupied.resize(new_capacity);

156         

157         size_t i;

158         size_t old_capacity;

159         

160         m_size = 0;

161         old_capacity = m_capacity;

162         m_capacity = new_capacity;

163         for (i = 0; i < m_capacity; ++i) {

164             m_occupied[i] = false;

165         }

166 

167         for (i = 0; i < old_capacity; ++i) {

168             if (old_occupied[i]) {

169                 insert(old_data[i]);

170             }

171         }

172         

173         old_data.clear();

174         old_occupied.clear();

175     }

176 };

177 

178 int main()

179 {

180     typedef long long KeyType;

181     HashSet<KeyType> hash;

182     string cmd;

183     KeyType data;

184     

185     while (cin >> cmd) {

186         if (cmd == "insert") {

187             cin >> data;

188             hash.insert(data);

189         } else if (cmd == "remove") {

190             cin >> data;

191             hash.remove(data);

192         } else if (cmd == "find") {

193             cin >> data;

194             cout <<  (hash.find(data) ? "true" : "false") << endl;

195         } else if (cmd == "clear") {

196             hash.clear();

197         } else if (cmd == "size") {

198             cout << hash.size() << endl;

199         } else if (cmd == "lambda") {

200             cout << hash.load_factor() << endl;

201         } else if (cmd == "end") {

202             break;

203         }

204     }

205     hash.clear();

206 

207     return 0;

208 }

我实现的一个HashMap,使用拉链法。当时偷了个懒没实现自定义类型,我错了:

  1 // My implementation for hash map.

  2 #include <iostream>

  3 #include <string>

  4 #include <vector>

  5 using namespace std;

  6 

  7 class HashMap {

  8 public:

  9     HashMap() {

 10         _buckets.resize(_bucket_num);

 11         int i;

 12         

 13         for (i = 0; i < _bucket_num; ++i) {

 14             _buckets[i] = nullptr;

 15         }

 16     };

 17     

 18     bool contains(int key) {

 19         key = (key > 0) ? key : -key;

 20         key = key % _bucket_num;

 21         LinkedList *ptr = _buckets[key];

 22         

 23         while (ptr != nullptr) {

 24             if (ptr->key == key) {

 25                 return true;

 26             }

 27         }

 28         

 29         return false;

 30     };

 31     

 32     int& operator [] (int key) {

 33         key = (key > 0) ? key : -key;

 34         key = key % _bucket_num;

 35         LinkedList *ptr = _buckets[key];

 36         

 37         if (ptr == nullptr) {

 38             _buckets[key] = new LinkedList(key);

 39             return _buckets[key]->val;

 40         }

 41         

 42         LinkedList *ptr2 = ptr->next;

 43         if (ptr->key == key) {

 44             return ptr->val;

 45         }

 46         

 47         while (ptr2 != nullptr) {

 48             if (ptr2->key == key) {

 49                 return ptr2->val;

 50             } else {

 51                 ptr = ptr->next;

 52                 ptr2 = ptr2->next;

 53             }

 54         }

 55         ptr->next = new LinkedList(key);

 56         ptr = ptr->next;

 57         return ptr->val;

 58     }

 59     

 60     void erase(int key) {

 61         key = (key > 0) ? key : -key;

 62         key = key % _bucket_num;

 63         LinkedList *ptr = _buckets[key];

 64         

 65         if (ptr == nullptr) {

 66             return;

 67         } else if (ptr->next == nullptr) {

 68             if (ptr->key == key) {

 69                 delete _buckets[key];

 70                 _buckets[key] = nullptr;

 71             }

 72             return;

 73         }

 74         

 75         if (ptr->key == key) {

 76             _buckets[key] = ptr->next;

 77             delete ptr;

 78             return;

 79         }

 80         

 81         LinkedList *ptr2;

 82         ptr2 = ptr->next;

 83         

 84         while (ptr2 != nullptr) {

 85             if (ptr2->key == key) {

 86                 ptr->next = ptr2->next;

 87                 delete ptr2;

 88                 return;

 89             } else {

 90                 ptr = ptr->next;

 91                 ptr2 = ptr2->next;

 92             }

 93         }

 94     }

 95     

 96     ~HashMap() {

 97         int i;

 98         LinkedList *ptr;

 99         

100         for (i = 0; i < _bucket_num; ++i) {

101             ptr = _buckets[i];

102             while (ptr != nullptr) {

103                 ptr = ptr->next;

104                 delete _buckets[i];

105                 _buckets[i] = ptr;

106             }

107         }

108         _buckets.clear();

109     }

110 private:

111     struct LinkedList {

112         int key;

113         int val;

114         LinkedList *next;

115         LinkedList(int _key = 0, int _val = 0): key(_key), val(_val), next(nullptr) {};

116     };

117 

118     static const int _bucket_num = 10000;

119     vector<LinkedList *> _buckets;

120 };

121 

122 int main()

123 {

124     HashMap hm;

125     string cmd;

126     int op1, op2;

127     

128     while (cin >> cmd) {

129         if (cmd == "set") {

130             cin >> op1 >> op2;

131             hm[op1] = op2;

132         } else if (cmd == "get") {

133             cin >> op1;

134             cout << hm[op1] << endl;

135         } else if (cmd == "find") {

136             cin >> op1;

137             cout << (hm.contains(op1) ? "true" : "false") << endl;

138         }

139     }

140     

141     return 0;

142 }

 

你可能感兴趣的:(数据结构与算法)