关于哈希表
来源:互联网 发布:美国大学不要面试知乎 编辑:程序博客网 时间:2024/06/06 14:06
学习完了有关二叉树的搜索结构,今天我们来接触另外一种结构,叫做哈希表。
1.什么是哈希表
哈希表:是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。它的所保存的数据和位置会有一种关系,这种关系我们叫做哈希函数。
2.构建哈希表的方法
常用的构建哈希表的方法有5种。
这5种方法分别是:
构建哈希表的方法有:
1)直接定址法: 该方法是取关键字的某个线性函数值为哈希地址
2)除留余数法: 它是用数据元素关键字除以某个常数所得的余数作为哈希地址。
3)折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。
4)随机数法::选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
5)*数学分析法:找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
一般常用的方法是直接定址法和数学分析法。下面我通过数学分析法来说明。
在这不得不说的是一个叫做哈希冲突的问题:因为你散列函数所计算的位置很可能有多个值,所以这多个值就会造成冲突,我们也就叫做哈希冲突,为了解决哈希冲突,研究了这几种方法。
3.开放定址法
也叫做闭散列法,我们在这里提供两种思路,一种是线性探测,一种是二次探测。
线性探测:线性探测说的就是,当你插入的一个数和所在位置上的数冲突了,这个时候,你就向后找,找到为空的位置进行存放这个数。
正是因为开放地址法的缺点,所以我们在对哈希表进行删除的时候,所要采取的删除方式采用懒惰删除,就是对需要删除的元素,我们不直接删除,而是给它一个标记,表示它已经被删除,我们不对它访问就好了。
另外,哈希的容量根据研究如果采用素数来说,冲突概率更小,所以我们提供一个素数表来取容量。
代码实现:
namespace open{ enum Status { EMPTY, DELETE, EXIST }; template<typename K, typename V> struct HashNode { HashNode(const K &key = K(), const V&value = V())//给出默认实参 : _key(key) , _value(value) , _status(EMPTY) { } K _key; V _value; Status _status; }; template<typename K> struct _Hashfunc { size_t operator ()(const K &key) { return key; } }; template<> struct _Hashfunc<string> { 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); } size_t operator ()(const string &key) { return BKDRHash(key.c_str()); } }; template<typename K, typename V, typename HashFunc = _Hashfunc<K>> class Hashtable { typedef HashNode<K, V> Node; typedef Hashtable<K, V, HashFunc> HashTable; public: Hashtable() :_size(0) { _ht.resize(GetNewSize()); } ~Hashtable() { ; } bool Insert(const K& key, const V& value) { //进行是否增容的检查。 _CheckCapacity(); size_t index = _HashFunc(key); while (_ht[index]._status == EXIST) { if (_ht[index]._key == key) return false; ++index; if (index == _size) index = 0; } _size++; _ht[index]._status = EXIST; _ht[index]._key = key; _ht[index]._value = value; return true; } bool Delete(const K& key) { size_t index = _HashFunc(key); size_t pos = index; while (_ht[index]._status != EMPTY) { if (_ht[index]._key == key) { _ht[index]._status = DELETE; _size--; return true; } ++index; if (pos == index) { break; } } return false; } Node* Find(const K& key) { size_t index = _HashFunc(key); size_t pos = index; while (_ht[index]._status != EMPTY) { if (_ht[index]._key == key&&_ht[index]._status == EXIST) { return &_ht[index]; } index++; if (pos == index) { break; } } return NULL; } protected: void _CheckCapacity() { //判断负载因子是否小于0.8 if (_ht.size() == 0 || _size * 10 / _ht.size() >= 8) { size_t newindex = 0; size_t newsize = GetNewSize(); HashTable tmp; tmp._ht.resize(newsize); for (size_t i = 0; i < _size; i++) { if (_ht[i]._status == EXIST) { tmp.Insert(_ht[i]._key, _ht[i]._value); } } this->Swap(tmp); } else return; } void Swap(Hashtable & t) { _ht.swap(t._ht); swap(_size, t._size); } size_t _HashFunc(const K& key) { //利用仿函数进行处理其他像string和结构体带来的问题。 HashFunc hf; return hf(key) % _ht.size(); } size_t GetNewSize() { 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 }; for (size_t i = 0; i < _PrimeSize; ++i) { if (_ht.size() < _PrimeList[i]) { return _PrimeList[i]; } } return 0; } protected: std::vector<Node> _ht; size_t _size; };}
4.拉链法
为了让哈希表更加高效,人们又想出了拉链法。也叫做哈希桶。
简单的说拉链法就是,当出现冲突的时候,我们在这个冲突的位置下面挂一个链表,这样,就可以轻松加愉快的解决冲突问题。
拉链法就可以很好的解决了开放地址法出现的一些问题。
当然,为了提高哈希表的效率,在拉链法中我们依然要考虑负载因子,这样我们就能更加高效的查找,这里的负载因子,最好是每一个哈希下只有一个节点。
当哈希表拉链法中拉链的节点增多的时候,这个时候查找的效率就很低,为了解决效率问题,这个时候我们可以提供一种思路,把链表改成一棵红黑树就好了,就从本来的O(N)到了O(logN)。这样就更加高效了。
代码实现:
namespace link{ template<typename K,typename V> struct KVNode { KVNode(const K& key=K(),const V& value=V()) : _key(key) , _value(value) , _next(NULL) {} K _key; V _value; KVNode<K, V> *_next; }; template<typename K> struct __Hashfunc { size_t operator() (const K &key) { return key; } }; template<> struct __Hashfunc<string> { 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); } size_t operator ()(const string &key) { return BKDRHash(key.c_str()); } }; template<typename K,typename V,typename HashFunc=__Hashfunc<K> > class HashTable { typedef KVNode<K, V> Node; typedef HashTable<K, V, HashFunc> Hashtable; public: HashTable() :_size(0) { _ht.resize(GetNewSize()); } ~HashTable() { Node* cur = NULL; Node* del = NULL; for (size_t i = 0; i < _ht.size(); i++) { cur = _ht[i]; if (_ht[i] == NULL) continue; while (cur) { del = cur; cur = cur->_next; delete del; del = NULL; } } } HashTable(const HashTable& ht) { _Copy(); } bool Insert(const K& key,const V& value) { CheckCapacity(); size_t index = Funcpos(key,_ht.size()); Node* newnode = new Node(key,value); if (_ht[index]) { Node* cur = _ht[index]; while (cur) { if (cur->_key == key) return false; cur = cur->_next; } } _size++; newnode->_next = _ht[index]; _ht[index] = newnode; return true; } bool Delete(const K& key) { size_t index = Funcpos(key,_ht.size()); Node* cur = _ht[index]; Node* prev = NULL; Node* del = NULL; while (cur) { if (cur->_key == key) { if (prev == NULL) { del = cur; delete cur; _ht[index] = NULL; } else { prev->_next = cur->_next; delete cur; } _size--; return true; } prev = cur; cur = cur->_next; } return false; } Node* Find(const K& key) { size_t index = Funcpos(key,_ht.size()); Node *cur = _ht[index]; if (_ht.empty()) { return NULL; } while (cur) { if (cur->_key == key) { return cur; } cur = cur->_next; } return NULL; } protected: void CheckCapacity() { if (_ht.size() == 0 || _size == _ht.size()) { size_t newsize = GetNewSize(); Hashtable tmp; tmp._ht.resize(newsize); Node* cur = NULL; Node* NewInsert = NULL; for (size_t i = 0; i < _ht.size(); i++) { //采用移动法进行交换 //就是把原来的vector上面的节点进行移动到新的vector上面。 cur = _ht[i]; if (cur == NULL) continue; while (cur) { size_t newpos = Funcpos(cur->_key, tmp._ht.size()); NewInsert = cur; cur = cur->_next; NewInsert->_next = tmp._ht[newpos]; tmp._ht[newpos] = NewInsert; } _ht[i] = NULL; } NewSwap(tmp); } } void Getnew() { Hash } void NewSwap( Hashtable &h) { _ht.swap(h._ht); //std::swap(_size, h._size); } size_t GetNewSize() { 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 }; for (size_t i = 0; i < _PrimeSize; ++i) { if (_ht.size() < _PrimeList[i]) { return _PrimeList[i]; } } return 0; } size_t Funcpos(const K& key,const size_t &size) { HashFunc h; return h(key) % size; } protected: std::vector<Node*> _ht; size_t _size; };}
5.哈希表的总结
对于哈希表,查找起来是相当方便,大致的效率为O(1),但是哈希表最大的问题就是太浪费空间。因为负载因子的原因,部分空间都是不用的,所以哈希也就是一种牺牲空间来提高时间的方式。另外哈希表不能够进行排序,作为一个使用key来决定储存位置的,它的key是关键,key当然不能重复。
代码已上传github:https://github.com/wsy081414
- 关于哈希表
- 关于哈希表
- 关于哈希表
- 关于哈希表大小
- 关于哈希表的总结
- 关于Java中的哈希表
- 数据结构::关于哈希表
- 【哈希表】关于map
- 关于
- 关于
- 关于++,--
- 关于#
- 关于。。。
- 关于
- 关于......
- 关于
- 关于
- 关于****
- MFC深度剖析消息反射机制
- 【图文信息】C#群发微信图文信息(Senparc.Weixin.MP.dll)
- php需要用到哪些工具
- copy mutableCopy
- 当启动tomcat时出现tomcat setting should be set in tomcat preference page
- 关于哈希表
- EasyUI的datagrid组件实现分页
- 如何创建一个类只能在堆(栈)上建立对象
- [hard]97. Interleaving String
- Redis 持久化策略
- ORA-07445: exception encountered: core dump [00000000] [SIGILL] [Illegal opcode] [0x000000000] [] []
- 日历控件的多选
- [生存志] 第23节 历代大事件概览 北宋
- 关于View转化成bitmap保存成图片