【数据结构】哈希表(线性探测法)
2018-03-28 22:41
375 查看
哈希表是一种搜索结构,当数据量大时,哈希搜索的效率高,平均时间复杂度O(1)。
【哈希查找】:
(1)在插入时,根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
(2)在搜索时,对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即散列方法(Hash Method),在散列方法中使用的转换函数叫着散列函数(Hash function),构造出来的结构叫散列表(Hash Table)。用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。
【哈希冲突】:
对于两个数据元素的关键字Ki和Kj(i != j),有Ki != Kj ( i != j) ,但HashFun( Ki ) ==HashFun( Kj ) ,将该种现象称为哈希冲突或哈希碰撞。
哈希表元素的插入:
【散列函数】:
常见的求哈希值的方法:
1.直接定址法:
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B。
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
(适合查找比较小且连续的情况)
2.除留余数法:
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数,按照哈希函数:Hash( key ) = key % p ( p <= m) ,将关键码转换成哈希地址。
3.平方取中法
4.折叠法
5.随机数法
6.数学分析法
【散列冲突处理方法】:
闭散列法:
在元素插入时遇到哈希冲突,我们可选择线性探查法处理冲突,还可以选择二次探查法处理冲突。
这里我们分析下线性探查法:
给出一组元素,它们的关键码为:37,25,14,36,49,68,57,11,散列表为HT[12],表的大小 m=12 ,假设采用Hash(key)= key % p ;(p=11)11是最接近m的质数,就有:
添加元素时,使用散列函数确定元素的插入位置,如果此空间有值:
1.该值是所要插入元素的关键码,不进行插入。
2.产生冲突,依次查看其后的下一个桶,如果发现空位置插入新元素
注意:
散列表的载荷因子:a = 插入元素个数 / 散列表的长度
a是散列表装满程度的标志因子。对于开放地址法,载荷因子非常重要,应严格限制在 0.7~0.8 以下。超过 0.8 ,查表时的CPU缓存按照指数曲线上升。
【代码实现】:
1.用vector开辟出一段空间来存放元素。
2.每个节点都有三种状态 EXIST(存在) 、DELETE(删除)、EMPTY(空) ,初始化时每个节点的状态设置为EMPTY。
【Hash.h】
【测试存入整型数据】
【测试以字符串为关键码存入】
【test.cpp】
【哈希查找】:
(1)在插入时,根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
(2)在搜索时,对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即散列方法(Hash Method),在散列方法中使用的转换函数叫着散列函数(Hash function),构造出来的结构叫散列表(Hash Table)。用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。
【哈希冲突】:
对于两个数据元素的关键字Ki和Kj(i != j),有Ki != Kj ( i != j) ,但HashFun( Ki ) ==HashFun( Kj ) ,将该种现象称为哈希冲突或哈希碰撞。
哈希表元素的插入:
【散列函数】:
常见的求哈希值的方法:
1.直接定址法:
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B。
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
(适合查找比较小且连续的情况)
2.除留余数法:
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数,按照哈希函数:Hash( key ) = key % p ( p <= m) ,将关键码转换成哈希地址。
3.平方取中法
4.折叠法
5.随机数法
6.数学分析法
【散列冲突处理方法】:
闭散列法:
在元素插入时遇到哈希冲突,我们可选择线性探查法处理冲突,还可以选择二次探查法处理冲突。
这里我们分析下线性探查法:
给出一组元素,它们的关键码为:37,25,14,36,49,68,57,11,散列表为HT[12],表的大小 m=12 ,假设采用Hash(key)= key % p ;(p=11)11是最接近m的质数,就有:
添加元素时,使用散列函数确定元素的插入位置,如果此空间有值:
1.该值是所要插入元素的关键码,不进行插入。
2.产生冲突,依次查看其后的下一个桶,如果发现空位置插入新元素
注意:
散列表的载荷因子:a = 插入元素个数 / 散列表的长度
a是散列表装满程度的标志因子。对于开放地址法,载荷因子非常重要,应严格限制在 0.7~0.8 以下。超过 0.8 ,查表时的CPU缓存按照指数曲线上升。
【代码实现】:
1.用vector开辟出一段空间来存放元素。
2.每个节点都有三种状态 EXIST(存在) 、DELETE(删除)、EMPTY(空) ,初始化时每个节点的状态设置为EMPTY。
【Hash.h】
#include<iostream> #include<vector> #include<assert.h> #include<utility> #include<cstring> using namespace std; //定义仿函数 template<class K> struct _HashFunc { size_t operator()(const K& key) { return key; } }; //特化string的版本 template<> struct _HashFunc<string> { static size_t BKDRHash(const char* str) { size_t seed = 131; // 31 131 1313 13131 131313 size_t hash = 0; while (*str) { hash = hash*seed + (*str++); } return (hash & 0x7fffffff); } size_t operator()(const string& key) { return BKDRHash(key.c_str()); //c_str()返回的是一个const char* 类型的字符串 } }; enum Status { EXIST, DELETE, EMPTY }; template<class K,class V> struct HashTableNode { //HashTableNode<K,V>* _pNode; K _key; V _value; Status _status; HashTableNode(const K& key=K(),const V& value=V()) :_key(key) , _value(value) , _status(EMPTY) { } }; //素数表,表内为哈希表的容量,素数降低哈希冲突 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 }; template<class K,class V,class HashFunc=_HashFunc<K>> class HashTable { typedef HashTableNode<K, V> Node; public: HashTable() {} HashTable(size_t size) { assert(size > 0); _v.resize(size); _size = 0; } //将K值转换成哈希值 size_t _HashTableFunc(const K& key) { HashFunc hf; //定义一个HashFunc的变量hf size_t hash = hf(key); //用变量hf调用HashFunc的仿函数,返回对应的整型 return hash% _v.size(); //算出哈希值,并返回 } pair<Node*, bool> Insert(const K& key, const V& value) { //检查是否需要扩容 CheckCapacity(); //对K值进行取余,判断插入位置 size_t index = _HashTableFunc(key); //如果存在,则循环着继续找 while (_v[index]._status !=EMPTY) { index++; if (index == _v.size()) index = 0; } _v[index]._key = key; _v[index]._value = value; _v[index]._status = EXIST; _size++; return make_pair<Node*, bool>(&_v[index], true); } Node* find(const K& key) //查找位置 { size_t index = _HashTableFunc(key); //如果存在,则继续寻找 while (_v[index]._status == EXIST) { //若相等,判断状态是否是删除 //若删除,则没找到,返回空 //若没删除,则返回该位置的地址 if (_v[index]._key == key) { if (_v[index]._status == DELETE) return NULL; return &_v[index]; } index++; if (index == _size) index = 0; } return NULL; } void Delete(const K& key) { //删除仅需要将状态修改 Node* delNode = find(key); if (delNode) delNode->_status = DELETE; } private: //交换两个哈希表 void Swap(HashTable<K, V>& h) { swap(_v, h._v); swap(_size, h._size); } void CheckCapacity() { //如果_v为空,则扩容到11 if (_v.empty()) { _v.resize(11); } //如果超过比例系数,则需要扩容 if (_size * 10 / _v.size() >= 7) { size_t index = 0; while (_PrimeList[index] < _v.size()) { index++; } size_t newSize = _PrimeList[index]; HashTable<K, V> newh(newSize); //新近一个哈希表 for (size_t i = 0; i < _v.size(); i++) //将旧的哈希表中的元素重新插入到新的哈希表 { if (_v[i]._status==EXIST) newh.Insert(_v[i]._key, _v[i]._value); } //交换两个哈希表 Swap(newh); } } private: vector<Node> _v; size_t _size; };
【测试存入整型数据】
void test() { int arr[] = { 3, 7, 12, 23, 45, 67, 13, 43 }; int size = sizeof(arr) / sizeof(arr[0]); HashTable<int, int> h1(11); for (int i = 0; i < size; i++) { h1.Insert(arr[i], arr[i]+ 3); } }
【测试以字符串为关键码存入】
void test2() { HashTable<string, string> h2(11); h2.Insert("abs", "1111"); h2.Insert("222","2222"); h2.Insert("223", "2223"); h2.Insert("224", "2224"); h2.Insert("225", "2225"); }
【test.cpp】
#include"Hash.h" int main() { //test(); test2(); return 0; }
相关文章推荐
- 数据结构:哈希表(除留取余法--线性探测法)
- C++数据结构--.哈希表线性探测开放寻址法与独立表链地址法
- 线性探测法构造哈希表(hash)
- 哈希表的C++实现(线性探测)
- 线性探测法构造哈希表(hash)
- 使用线性探测法构造哈希表
- 哈希表---线性探测再散列(hash)
- C++ 哈希表 线性探测 二次探测 哈希桶
- 哈希表的构造之线性探测法
- 哈希表(开放寻址,线性探测)
- 线性探测法构造哈希表(hash)
- 详细图解什么叫平方探查法即二次探测再散列和线性探测再散列(数据结构 哈希函数 哈希冲突)
- 数据结构学习---线性表;树;哈希表进行查找的区别
- 哈希表——线性探测法、链地址法、查找成功、查找不成功的平均长度
- 线性探测法构造哈希表(hash)
- 利用线性探测法构造哈希表
- 线性表,链表,哈希表是常用的数据结构,在进行Java开发时
- 哈希表中线性探测再散列法及等概率条件下平均查找长度
- 线性探测法构造哈希表(hash)
- 哈希表开放寻址法之线性探测法解决冲突问题