概述
哈希表又称散列表,是根据关键码值(Key,value)直接进行访问的数据结构。哈希结构中存在一种函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,以便于在查找时通过该函数可以很快找到该元素。
这种函数称为哈希函数。
插入元素
根据插入元素的关键码值,用哈希函数计算出插入位置进行存放。
搜索元素
用哈希函数计算出该元素的存储位置,然后在结构中按照此位置读取数据进行比较,如果关键码值相等,则搜索成功。
哈希函数
如果现在有如下哈希函数:hash(key) = key % capacity;
即,元素的存储位置在关键码值对哈希结构容量取余所得的位置
使用这种方法进行搜索不需要进行多次比较,因此效率很高。
常见的哈希函数
- 直接定制法–(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况 - 除留余数法–(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
哈希冲突
思考:如果出现两个值用哈希函数计算出来的哈希地址是相同的怎么办?
这种情况叫做哈希冲突,即:不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
引起哈希冲突通常是因为哈希函数设计不合理;
哈希函数的设计原则:
- 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0 到m-1之间
- 哈希函数计算出来的地址能均匀分布在整个空间中
- 哈希函数应该比较简单
解决哈希冲突有两种办法:闭散列、开散列
闭散列
闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,就在哈希函数计算出来的位置向后走,走到下一个空位置进行插入。
线性探测
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
插入:
- 通过哈希函数计算待插入元素的哈希地址
- 如果该位置没有元素就插入,如果有元素就用线性探测找到下一个空位置进行插入
删除:注意!!!采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。所以我们这里使用的是假删除法,即给哈希表中每个空间一个标记,用来表示该位置是否有元素,如下:
enum STATE {
EXIST, //表示该位置已有元素
DELETE, //表示该位置元素已被删除
EMPTY //表示该位置为空
};
闭散列增容
哈希表什么情况下进行增容?如何进行?
散列表的负载因子 α=表中有效元素个数/表的长度
负载因子α用来表示散列表的装满程度,α越小,表越空;α越大,表越满。
通常α的大小不会超过一定的阈值,因为表越满,线性探测的效率越低,所以为了保证效率,一般将α的值定在0.7~0.8之间,超过这个值就需要进行增容。
增容:创建一个新表,将旧表中的元素重新计算位置并插入到新表,最后交换旧表和新表(表的长度也要交换)
void checkCapacity()
{
//负载因子:当前有效元素个数/容量 一般 < 1
//if (_size / _hTable.size() >= 0.7) 转换成下面的写法更方便比较
if (_hTable.size() == 0 || _size * 10 / _hTable.size() >= 7)
{
//开新表
int newC = _hTable.size() == 0 ? 10 : 2 * _hTable.size();
HashTable <K, V> newht(newC);
for (size_t i = 0; i < _hTable.size(); i++)
{
if (_hTable[i]._state == EXIST)
{
newht.insert(_hTable[i]._kv);
}
}
Swap(newht);
}
}
void Swap(HashTable<K, V>& ht)
{
swap(_hTable, ht._hTable);
swap(_size, ht._size);
}
闭散列–线性探测代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
using namespace std;
//哈希:闭散列
//哈希表:线性探测
enum STATE {
EXIST,
DELETE,
EMPTY
};
template<class K,class V>
struct HashNode{
pair<K, V> _kv;
STATE _state=EMPTY;
};
//顺序表实现哈希
template<class K,class V>
class HashTable {
public:
typedef HashNode<K, V> Node;
HashTable(size_t n=10)
:_hTable(n)
,_size(0)
{
}
bool insert(const pair<K, V>& kv)
{
//0.检查容量
checkCapacity();
//1.计算哈希位置
int idx = kv.first % _hTable.size();
//2.判断key是否存在:哈希不能存放重复的数据
while (_hTable[idx]._state != EMPTY)
{
//如果当前位置数据有效,并且key相同,插入失败
if (_hTable[idx]._state == EXIST && kv.first == _hTable[idx]._kv.first)
{
return false;
}
//继续搜索
++idx;
if (idx == _hTable.size(