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 }