目录
1.哈希概念(主要应用于频繁查找的场景)
2. 哈希冲突
3.解决哈希冲突
3.1哈希函数的选择
1. 直接定制法--(常用)
2. 除留余数法--(常用)
3. 平方取中法--(了解)
4. 折叠法--(了解)
5. 随机数法--(了解)
6.数学分析法--(了解)
3.2.闭散列和开散列
3.2.1闭散列
1. 线性探测
2.二次探测:
3.2.2.开散列
1.开散列概念
2.代码实现
3.范型模板兼容问题
4.容量为素数时候模除计算桶位置哈希冲突最少
5. 开散列增容
6.关于黑客闲着没事攻击你的哈希表
对于两个数据元素的关键字 和 (i != j),有ki !=kj ,但有:Hash(ki ) == Hash( kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
也就是我们下面的例子
//*****闭散列*******//
#pragma once
#include
#include
using namespace std;
//我们可以使用 #define 定义常量,为什么非要使用枚举? 枚举的优点:
//1. 增加代码的可读性和可维护性
//2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
//3. 防止了命名污染(封装)
//4. 便于调试
//5. 使用方便,一次可以定义多个常量
//6. 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
enum State{ EXIST,EMPTY,DELETE };
//哈希中存放的元素我们约定存放的元素不可以重复
template
struct element
{
element(const T& key=T())
:_key(key),_state(EMPTY)
{}
T _key;
State _state;
};
template
class my_hash{
public:
my_hash(size_t capacity=10)
:_capacity(capacity),_size(0),_total(0),_con(capacity)
{}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
//插入元素:要想插入散列表就要先判断当前元素的插入位置是否有了元素,如果有元素那么
//就线性探测向后寻找要插入元素的位置
bool insert(const T& val)
{
//插入之前先判断当前元素是否已经存在:
if (find(val).second)
{
return false;
}
check_capacity();
size_t val_addr = get_index(val);
int i = 0;//二次探测,探测的次数
//寻找要插入元素位置:
while (_con[val_addr]._state != EMPTY)
{
//如果当前要插入元素已经存在那么就返回false插入失败,这里必须要加EXIST因为
//这里我们插入元素实际给原有的初始化空间赋值(意思就是我们这里用vector做为底层容器
//那么默认初始化的元素为0,如果我们当前要插入的元素刚好也是0相同那么这里如果不加_state==EXIST
//的话空间中元素相同那么就认为当前元素已经存在,但是其标志位state其实还是EMPTY,逻辑上是不存在的
if (_con[val_addr]._state==EXIST&&_con[val_addr]._key == val)
{
return false;
}
else if (_con[val_addr]._state == DELETE&&_con[val_addr]._key == val)
{//这里处理的情况是当前空间的元素被删除了只是逻辑上的删除那么我们可以在该位置插入元素
//而不需要更新_total因为当前位置原本就被算入到total中
_con[val_addr]._state = EXIST;
_con[val_addr] = val;
_size++;
}
if (isline)
{//线性探测
val_addr++;
if (val_addr == _capacity)
{
val_addr = 0;
}
}
else
{//二次探测
val_addr =( val_addr + 2 ^ i + 1)%_capacity;
i++;
}
}
_con[val_addr]._key = val;
_con[val_addr]._state = EXIST;
_size++;
_total++;
return true;
}
void swap(my_hash& t)
{
_con.swap(t._con);
std::swap(t._size, _size);
std::swap(t._capacity, _capacity);
std::swap(t._total, _total);
}
pair find(const T& val)
{
size_t val_addr = get_index(val);
int i = 0;
while (_con[val_addr]._state!=EMPTY)
{
if (_con[val_addr]._key == val)
{
if (_con[val_addr]._state == DELETE)
//如果当前元素被删除了那么就返回false
{
return make_pair(npos, false);
}
return make_pair(val_addr,true);
}
if (isline)
{//线性探测
val_addr++;
if (val_addr == _capacity)
{
val_addr = 0;
}
}
else
{//二次探测:为什么这里不像上面一样超过当前容量就将哈希地址赋值为0呢?
//因为如果探测次数变多的话那么每次加的地址就会变大,有可能到后面每次加的地址偏移量都大于容量大小那么就会
//陷入一个死循环
val_addr = (val_addr + 2 ^ i + 1) % _capacity;
i++;
}
}
//找不到就返回npos;
return make_pair(npos, false);
}
bool erase(const T& val)
{
if (find(val).second == false)
{
return false;
}
//只有当addr存在才可以删除
size_t erase_addr = find(val).first;
_con[erase_addr]._state = DELETE;
--_size;//更新有效元素个数
return true;
}
static size_t npos;
private:
//判断当前的哈希散列表是否要扩容:
//当哈希的载荷因子大于0.7时就需要扩容,载荷因子=填入表中元素个数/散列表的长度
void check_capacity()
{
if (_total * 10 / _capacity >= 7)
{
my_hash newcon(2 * _capacity);
for (int i = 0; i < _capacity; i++)
{
if (_con[i]._state == EXIST)
{
newcon.insert(_con[i]._key);
}
}
swap(newcon);//这里如果不加this->会如何调用的当前类中的swap还是std中的swap
}
}
//哈希中元素位置:
size_t get_index(const T& val)
{
return val%_capacity;
}
size_t _capacity;
vector> _con;
size_t _size;//有效元素个数
//哈希散列表中存放的总的元素个数这里包括已删除的元素。为什么要存放有效总的元素个数呢?
//我们知道delete位置是不可以插入元素的因为如果在delete位置插入元素的话那么,有可能
//当前元素是已经插入过了的,只是因为哈希冲突存放在了后面,那么我们直接在delete插入
//就有可能将后面已经存在的元素再次插入哈希表中。从而导致哈希表中存放了相同的元素
//因为状态为delete的位置不可以插入元素那么我们可以将delete位置也当作一个有效元素来
//看待所以我们在扩容的时候要用有效元素个数加上delete位置个数来计算是否要扩容。
//一句话总结上面的内容就是:因为被删除位置是不可以插入元素的,所以我们在计算是否要
//扩容的时候要用有效元素个数+被删除位置元素个数来计算。
size_t _total;
};
template
size_t my_hash:: npos = -1;
void test()
{
my_hash h(10);
h.insert(22);
h.insert(23);
h.insert(24);
h.insert(22);
h.insert(32);
h.insert(42);
h.insert(7);
h.insert(8);
h.insert(42);
h.insert(19);
h.insert(99);
cout << h.size() << endl;
h.erase(19);
cout << h.size() << endl;
if (h.find(99).second)
{
cout << "99 is in hash,index=" << h.find(99).first<
#pragma once
#include
#include
#include"primeList.h"
using namespace std;
//开散列:(拉链法-链地址法-哈希桶) 原理:数组+链表
template
struct HashBucketNode
{
public:
HashBucketNode(const V& data)
: _next(nullptr), _data(data)
{}
HashBucketNode* _next;
V _data;
};
//将字符串转为整型:
template
class STRTOI
{
public:
size_t operator()(const T *str)
{
return BKDRHash(str);
}
private:
size_t BKDRHash(const T *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch;
}
return hash;
}
};
//哈希中默认传入的是整型
template
class BASEC_TYPE
{
public:
size_t operator()(const T& val)
{
return val;
}
};
template>
class hashbucket
{
public:
typedef HashBucketNode node;
hashbucket(size_t capacity=7)
:_con(capacity),_size(0),_capacity(capacity)
{}
//允许插入重复元素
bool insert_repeat_element(const T& val)
{//那么直接头插
ChekeCapacity();
size_t bucket_addr = get_bucket(val);
node* newnode = new node(val);
newnode->_next = _con[bucket_addr];
_con[bucket_addr] = newnode;
_size++;
return true;
}
//删除所有重复元素,返回删除重复元素个数
size_t erase_repeat_element(const T& val)
{
size_t bucket_addr = get_bucket(val);
node* cur = _con[bucket_addr];
node* prev = nullptr;
size_t oledsize = _size;
while (cur)
{
if (cur->_data == val)
{
if (prev == nullptr)
{//删除头节点
prev = cur;
cur = cur->_next;
delete prev;
prev = nullptr;
_con[bucket_addr] = cur;
}
else
{//否则删除中间节点
prev->_next = cur->_next;
delete cur;
cur = prev->_next;
}
_size--;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return oledsize - _size;
}
//插入元素为唯一值
bool insert_unique_element(const T& val)
{
ChekeCapacity();
//获取元素要插入桶的位置:
size_t bucket_addr = get_bucket(val);
if (find(val))
{//如果在桶中找到当前元素那么就插入失败返回false
return false;
}
else
{//如果没有找到那么头插到当前桶中
node* newnode = new node(val);
newnode->_next = _con[bucket_addr];
_con[bucket_addr] = newnode;
_size++;
return true;
}
}
//删除唯一元素:
bool erase_unique_element(const T& val)
{
size_t bucket_addr = get_bucket(val);
node* cur = _con[bucket_addr];
node* prev = nullptr;
while (cur)
{
if (cur->_data == val)
{
if (prev == nullptr)
{//删除头节点
prev = cur;//用prev将当前要删除节点保存
cur = cur->_next;//保存头节点的下一个
_con[bucket_addr] = cur;//更新头节点
delete prev;//删除原来头节点
}
else
{
prev->_next = cur->_next;
delete cur;
}
_size--;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
node* find(const T& val)
{
//获取要查找元素所在桶
size_t bucket_addr = get_bucket(val);
node* cur = _con[bucket_addr];
while (cur)
{
if (cur->_data == val)
{
return cur;
}
else
{
cur = cur->_next;
}
}
return nullptr;
}
size_t bucket_count()
{
return _con.capacity();
}
//辅助方法:打印当前哈希桶中所有元素帮助我们检测当前哈希桶是否实现正确
void print_bucket()
{
for (int i = 0; i < _capacity; i++)
{
node* cur = _con[i];
cout << "bucket[" << i << "]:";
while (cur)
{
cout << cur->_data << "--->";
cur = cur->_next;
}
cout << "nullptr" << endl;
}
cout << "===========================================" << endl;
}
size_t size()
{
return _size;
}
bool empty()
{
return _size == 0;
}
void swap(hashbucket& t)
{
_con.swap(t._con);
std::swap(t._size, _size);
std::swap(t._capacity, _capacity);
}
void ChekeCapacity()
{
//开散列最好的情况就是散列表中每条链表只挂一个元素也就是当我们散列表长度=元素个数那么就需要扩容了
if (_size == _capacity)
{
size_t new_capacity = GetNextPrime(_capacity);
vector new_bucket(new_capacity);
for (int i = 0; i < _capacity; i++)
{
node* cur = _con[i];//将当前哈希桶中的节点挂到新建哈希桶中
while (cur)
{
cur->_next = new_bucket[get_bucket(cur->_data, new_capacity)];
new_bucket[get_bucket(cur->_data, new_capacity)] = cur;
cur = cur->_next;
}
}
_con.swap(new_bucket);
}
}
~hashbucket()
{
for (int i = 0; i < _capacity; i++)
{
node** cur = &_con[i];
while (*cur)
{
node* next = (*cur)->_next;
delete *cur;
*cur = nullptr;
*cur = next;
}
}
}
private:
//获取哈希元素在所在哈希桶的位置:
size_t get_bucket(const T& val)
{
TYPE type;
return type(val)%_capacity;
}
size_t get_bucket(const T& val,const size_t capacity)
{
TYPE type;
return type(val) % capacity;
}
size_t GetNextPrime(size_t prime) {
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
private:
vector _con;
size_t _size;
size_t _capacity;
};
包含的primeList.h文件:
#pragma once
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
};