hash的由来和概念
1.hash的由来:
①:由于在树形结构中,不管怎样去查找一个数据,都会存在去比较,通过一次次的比较来找到那个数,顺序查找的时间复杂度为O(N),而树形结构的平均查找时间复杂度为log2(N),搜索效率取决于比较的次数。
②:所以我们需要一个结构,这样的结构可以不经过任何比较,一次就可以得到想要搜索的数据,所以我们引出了哈希结构。
2.哈希的概念:
通过哈希可以使元素的存储位置与它的关键码形成一一映射的关系,那么在查找的时候就可以快速的找到需要的元素。
hash的介绍
通过上述的概念,所以我们对哈希结构的存储应该具有以下两种的需求:
1:插入元素
通过所插入元素的关键码,通过哈希函数找到其应该插入的位置,然后进行插入操作。
2:搜索元素
对搜索的元素进行与插入同样的哈希函数的操作,得到元素插入的位置,然后在其位置找元素进行一一比较,最后搜索得到元素。
哈希函数
哈希函数和哈希冲突是哈希结构中非常重要的两个角色,他们的设定对哈希函数的计算效率有着很大的影响下面我们先看哈希函数:
1.哈希函数的设计规则:
- 哈希表的定义域必须包含所需要存储数据的所有关键码,其中如果有m个关键码需要插入,那么就表就必须有0~m-1个位置。
- 哈希函数计算出来的数据能均匀分布在表中的每一个位置。
- 哈希函数的设定应该简单。
一般情况下,哈希函数的设定可以减少哈希冲突的产生。
2.常见的哈希函数:
①:除留余数法:假设表中的数据为m个,那么我们取一个不大于m但是接近于m的一个质数p来充当除数,将每个需要插入的关键码对p进行取余,余数即为该数要插入的表的下标。(取质数的原因是经过大量的数据检验,取质数的时候,数据的分布很均匀,能更好的取利用内存)
②:直接定址法:取关键字的某个线性函数为地址:
Hash(key) = A*key+B 其中A和B是常数,通过这样一个函数直接定位数据应该插入的位置。(用于查找比较小且连续的情况)
③:平方取中法:对关键值取平方,然后取其中间的数为哈希地址:如关键码是1234,它的平方是1522756,那么就取227为哈希地址插入。(该方法适用于不知道关键字的分布,但是位数又不是很大)
④:折叠法:将关键字从左到右分割成几个小部分,最后一部分可以小一点,然后将这几个小部分进行相加,然后按散列表的表长,取最后几位作为哈希地址。(适用于不知道关键字的分布,并且关键字的位数比较大)
⑤:随机数法:选择一个随机函数,取关键字的随机函数值作为其哈希地址。(关键字长度不等的时候适用)
⑥:数学分析法:假设关键码有n位,但是总会有那么几位在所有关键码中出现的次数均匀,所以我们可以根据这几个均匀的码和表的大小,提取出哈希地址。(适用于关键字位数比较大,事先知道关键字和关键字中数值的分布情况)
其中注意的是:
- 哈希函数设定的越精妙,哈希冲突的情况就会减少,但是不可能避免。
- 其中对前两种方法使用的比较多(除留余数法和直接定址法)。
哈希冲突以及解决办法
1.哈希冲突:对于两个截然不同的数据,通过哈希函数的计算得到相同的哈希地址。
2.哈希冲突的解决办法分为以下两种:闭散列和开散列。
闭散列
1.闭散列:也叫开放地址法,当出现哈希冲突时,如果哈希表没有满,那就一定有空位置,此时可以将其插入到“下一个”空位置中去。
而寻找下一个位置有以下两种方法:
①:线性探测:
方法:当发生哈希冲突,则从需要插入的位置依次向后寻找空位进行插入。
插入:
- 通过哈希函数找到哈希地址码。
- 如果该位置没有元素那么直接插入,如果有元素那么就往后依次寻找空位进行插入。
删除:
- 使用这个方法删除的时候并不是真正的删除,因为如果存在哈希冲突,对一个数据进行删除后,那么查找其与被删除位置冲突的元素的时候那么就会出现错误,所以我们应该采用伪删除的方法进行删除操作。
线性探测的代码实现:
#include<iostream>
using namespace std;
const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] =
{
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
};
// 注意:假如实现的哈希表中元素唯一,即key相同的元素不再进行插入
// 为了实现简单,此哈希表中我们将比较直接与元素绑定在一起
namespace Close_Hash
{
enum State{
EMPTY, EXIST, DELETE };
template<class K, class V>
class HashTable
{
struct Elem
{
pair<K, V> _val;
State _state;
};
public:
HashTable(size_t capacity = 3)
: _ht(capacity), _size(0), _totalSize(0)
{
for (size_t i = 0; i < capacity; ++i)
_ht[i]._state = EMPTY;
}
// 插入
bool Insert(const pair<K, V>& val)
{
CheckCapacity();
int index = HashFunc(val.first);
while (_ht[index]._state != EMPTY)
{
if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first == key)
return false;
index++;
if (index >= _totalSize)
index = 0;
}
_ht[index]._val = val;
_ht[index]._state = EXIST;
_size++;
_totalSize++;
return true;
}
// 查找
size_t Find(const K& key)
{
int index = HashFunc(key);
while (_ht[index]._state != EMPTY && _ht[index]._val.first != key)
{
index++;
if (index >= _totalSize)
index = 0;
}
if (_ht[index]._state == EMPTY)
return -1;
else
return index;
}
// 删除
bool Erase(const K& key)
{
int index = Find(key);
if (index != -1)
{
_ht[index]._state = DELETE;
--_size;
return true;
}
return false;
}
size_t Size()const
{
return _size;
}
bool Empty() const
{
return _size == 0;
}
void Swap(HashTable<K, V>& ht)
{
swap(_size, ht._size);
swap(_totalSize, ht._totalSize);
_ht.swap(ht._ht);
}
private:
size_t HashFunc(const K& key)
{
return key % _ht.capacity();
}
HashTable<K, V> GetNextPrime(int cp)
{
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > cp)
{
return primeList[i];
}
}
return primeList[i];
}
void

哈希是一种通过哈希函数将元素映射到固定大小表中,实现快速查找的技术。哈希冲突通过闭散列(开放地址法)和开散列(链地址法)解决,其中闭散列易导致冲突堆积,开散列利用链表解决冲突。布隆过滤器结合位图和哈希,通过多个哈希函数减少空间占用,但存在误判可能。位图用于快速判断数据是否存在,而布隆过滤器适用于海量数据的可能存在性查询,但不支持精确查找和删除操作。
最低0.47元/天 解锁文章
2483





