HashTable-散列表/哈希表,是根据关键字(key)而直接访问在内存存储位置的数据结构。
它通过一个关键值的函数将所需的数据映射到表中的位置来访问数据,这个映射函数叫做散列函数,存放记录的数组叫做散列表。
构造哈希表的几种方法
直接定址法--取关键字的某个线性函数为散列地址,Hash(Key)= Key 或 Hash(Key)= A*Key + B,A、B为常数。
除留余数法--取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址。Hash(Key)= Key % P。
平方取中法
折叠法
随机数法
数学分析法
不同的Key值经过哈希函数Hash(Key)处理以后可能产生相同的值哈希地址,我们称这种情况为哈希冲突。任意的散列函数都不能避免产生冲突。
一.处理哈希冲突的闭散列方法
1.线性探测
2.二次探测
二.处理哈希冲突的开链法(哈希桶)
此处为哈希桶法
#pragma once #include<iostream> #include<vector> using namespace std; template<class K,class V> struct kv { K _key; V _value; kv<K, V>* _next; kv() {} kv(const K& key,const V& value) :_key(key) , _value(value) , _next(NULL) {} }; template<class K,class V> class HashTableBucket { public: //_table掉自己的默认构造函数 HashTableBucket() :_size(0) {} ~HashTableBucket() { _size = 0; } bool Insert(const K& key, const V& value) { if (_size == _table.size())//平均每个上面都有数据,扩容 { size_t capacity = _CheckCapacity(); vector<kv<K, V>*> tmp; tmp.resize(capacity); for (int i = 0; i < _table.size(); ++i) { kv<K, V>* cur = _table[i]; kv<K, V>*pre =NULL; while (cur) { pre = cur; cur = cur->_next; size_t index = _HashFunc(pre->_key,tmp.size()); pre->_next = tmp[index]; tmp[index] = pre; } } _table.swap(tmp); } size_t index = _HashFunc(key, _table.size()); kv<K, V>* cur = _table[index]; while (cur) { if (cur->_key == key) return false; cur = cur->_next; } //头插方便 kv<K,V>* tmp = new kv<K,V>(key, value); tmp->_next = _table[index]; _table[index] = tmp; ++_size; } kv<K, V>* Find(const K& key) { size_t index = _HashFunc(key, _table.size()); kv<K, V>* cur = _table[index]; while (cur) { if (cur->_key == key) return cur; cur = cur->_next; } return NULL; } bool Remove(const K& key) { size_t index = _HashFunc(key, _table.size()); kv<K, V>* cur = _table[index]; kv<K, V>* prev = NULL; while (cur&&cur->_key!=key) { prev = cur; cur = cur->_next; } if (cur==NULL) return false; else { if (cur == _table[index]) _table[index] = cur->_next; else { if (cur->_next) prev->_next = cur->_next; else prev->_next = NULL; } delete cur; --_size; } } void Print() { for (int i = 0; i < _table.size(); ++i) { kv<K, V>* cur = _table[i]; cout << i; while (cur) { printf("(%d,%s)===", cur->_key, cur->_value.c_str()); cur = cur->_next; } cout << "NULL" << endl; } cout << endl; } protected: size_t _CheckCapacity() { // 使用素数表对齐做哈希表的容量,降低哈希冲突 const int _PrimeSize = 28; static const unsigned long _PrimeList[_PrimeSize] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; size_t i = 0; for (; i < _PrimeSize; ++i) { if (_PrimeList[i]>_size) return _PrimeList[i]; } return _PrimeList[_PrimeSize]; } size_t _HashFunc(const K& key, size_t capacity) { return key%capacity; } protected: //用kv结构的_next标识是否有数据,不需_statu vector<kv<K, V>*> _table;//_table.size()等同于HashTableBucket的capacity,可用容量 size_t _size; }; void Test1() { HashTableBucket<int, string> hs; hs.Insert(1, "abcd"); hs.Insert(2, "bcde"); hs.Insert(54, "cdef"); hs.Insert(10, "oocd"); hs.Insert(12, "lppd"); hs.Insert(106, "uucd"); for (int i = 53; i < 106; ++i) { hs.Insert(i, "code"); } hs.Print(); /*kv<int,string> *ret = hs.Find(1); if (ret) cout << ret->_value.c_str() << endl; hs.Remove(1); ret = hs.Find(1); if (ret) cout << ret->_value.c_str() << endl;*/ hs.Remove(97); hs.Remove(98); hs.Print(); }
拓展:对于key非数值来说,无法直接用key%capacity直接获得index,
template<class K> struct HashFuncDefault { size_t operator()(const K& key) { return key; } }; static size_t BKDRHash(const char * str) { unsigned int seed = 131; // 31 131 1313 13131 131313 unsigned int hash = 0; while (*str) { hash = hash * seed + (*str++); } return (hash & 0x7FFFFFFF); } //模板特化 template<> struct HashFuncDefault<string> { size_t operator()(const string& st) { return BKDRHash(st.c_str()); } }; //并修改HashTableBucket中函数如下即可: size_t _HashFunc(const K& key, size_t capacity) { return HashFuncDefault<K>()(key) % capacity; } //测试用例 void Test2() { HashTableBucket<string, string> hs; hs.Insert("微软", "Microsoft"); hs.Insert("软件", "software"); //hs.Print();//打印类型不匹配,需修改 } //调试窗口
总结:
哈希表的插入,删除时间复杂度为O(1)
注意负载因子的处理和哈希函数的设计