一.哈希及其概念
- 通过一定的哈希函数,确定元素的存储位置。搜索效率较高,为O(1)
- 负载因子:存储元素个数/总表格长度 若大于一定值需要扩容
二.常见的哈希函数
直接定址法:hashfunc(date)=A*date+B;【A,B为常数】
- 除留余数法:hashfunc(date)=date%capacity【capacity为表格容量】
- 平方取中法:将数据平方后取中间三位。
- 折叠法:例 12345 12+34+5=51
三.哈希冲突
- 不同元素计算出的存储位置(哈希地址)相同
四.解决哈希冲突
-
1.前期需有效减少哈希冲突的发生--------》哈希函数设计要合理
哈希函数的值域必须在表格范围内
哈希函数要尽可能使元素分布均匀
哈希函数要尽可能简单 -
2.发生哈希冲突后如何解决
闭散列:从哈希冲突的位置,找下一个空余位置插入元素
开散列:将哈希冲突的元素按链表的形式挂在结点位置
五.闭散列
从哈希冲突的位置,找下一个空余位置插入元素。寻找方式有两种,线性探测和二次探测。
1.线性探测
直接向后+1查找。
- 优点:简单
- 缺点:容易造成数据堆积。一个冲突造成很多数据都冲突。
2.二次探测
H(i)=H0+i^2; 或者H(i)=H0- i^2;
- 优点:解决了数据堆积问题
- 缺点:可能要查找多次
3.初始化,需设置标志位【EX,EM,DE】
DE需要的原因,此位置不可以插入元素,也不可设置为EM 。查找时遇EM就停止了,可能出现错误。
4扩容方式
1.不可以按照vector方式扩容。表格长度增加,相应的元素位置也会改变。
2.先新建哈希表,将元素一个一个重新插入到新表格。
六.开散列
将哈希冲突的元素按链表的形式挂在结点位置。每个位置叫做一个哈希桶。
1.扩容方式
1.不按照vector方式扩容,也不按照闭散列方式扩容【新建结点,空间开销大】
2.将表格结点直接插入新表格即可。
2.扩容节点
当表格中每个位置都有元素时,扩容!
七.实现代码
-
- 闭散列头文件 haxi.hpp
#pragma once
#include<vector>
#include<iostream>
#include<string>
#include"common.h"
enum state{ EMPTY,EXIST,DELETE };
template<class T>
struct Elem{
Elem(const T& date=T())
:_date(date)
,_state(EMPTY)
{}
T _date;
state _state;
};
//约定:哈希表格中的元素必须唯一
template<class T,class DF=DFDef<T>,bool isline=true>
class hashtable {
public:
hashtable(size_t capacity= Getnextprime(10))
:_size(0)
,_total(0)
{
_table.resize(capacity);
}
bool insert(const T& date) {
checkcapacity();
//1.通过哈希函数,计算哈希地址
size_t hashaddr = hashfunc(date);
size_t i = 0;
while (_table[hashaddr]._state != EMPTY) {
//元素已存在
if (_table[hashaddr]._state == EXIST && _table[hashaddr]._date == date)
return false;
if (isline) {
//线性探测
hashaddr++;
if (hashaddr == _table.capacity()) {
hashaddr = 0;
}
}
else {
//二次探测
i++;
hashaddr += 2 * i + 1;
//方式一
hashaddr %= _table.capacity();
}
}
//插入元素
_table[hashaddr]._date = date;
_table[hashaddr]._state = EXIST;
++_size;
++_total;
return true;
}
int find(const T& date) {
size_t hashaddr = hashfunc(date);
size_t i = 0;
while (_table[hashaddr]._state != EMPTY) {
if (_table[hashaddr]._state == EXIST && _table[hashaddr]._date == date) {
return hashaddr;
}
if (isline) {
//线性探测
hashaddr++;
if (hashaddr == _table.capacity()) {
hashaddr = 0;
}
}
else {
//二次探测
i++;
hashaddr += 2 * i + 1;
//方式一
hashaddr %= _table.capacity();
}
}
return -1;
}
bool erase(const T& date) {
int pos = find(date);
if (pos == -1) {
return false;
}
_table[pos]._state = DELETE;
_size--;
return true;
}
void swap(hashtable<T, DFstr, isline>& ht) {
_table.swap(ht._table);
std::swap(_size, ht._size);
std::swap(_total, ht._total);
}
size_t size()const {
return _size;
}
private:
void checkcapacity() {
if (_total*10 / _table.capacity() >= 7) {
//扩容方式:将元素重新哈希函数确定位置!然后插入新空间
//1.新创建一个哈希表
hashtable<T, DFstr, isline> newHt(Getnextprime(_table.capacity()));
//插入元素
for (auto e : _table) {
if (e._state == EXIST) {
newHt.insert(e._date);
}
}
swap(newHt);
}
}
size_t hashfunc(const T& date) {
return DF()(date) % _table.capacity();
}
std::vector<Elem<T>> _table;
size_t _size;
size_t _total;
};
void test2(){
hashtable<std::string, DFstr, false> ht;
ht.insert("1111");
ht.insert("2222");
ht.insert("3333");
ht.insert("4444");
}
- 2.开散列头文件hashbucket.hpp
#pragma once
#include"common.h"
#include<vector>
#include<iostream>
template<class T>
struct HashNode {
HashNode(const T& date=T())
:_pnext(nullptr)
,_date(date){}
HashNode<T>* _pnext;
T _date;
};
template<class T,class DF=DFDef<T>>
class hashbucket {
typedef HashNode<T> Node;
public:
hashbucket(size_t capacity = 9)
:_size(0)
{
_table.resize(Getnextprime(capacity));
}
bool insertunique(const T& date) {
checkcapacity();
//1.通过哈希函数计算桶号
size_t bucketnum = hashfunc(date);
std::cout <<date<<" :"<< bucketnum << "\n";
//2.检测date元素在哈希桶中是否存在
Node *pcur = _table[bucketnum];
while (pcur) {
if (pcur->_date == date)
return false;
pcur = pcur->_pnext;
}
//3.插入新节点
//采用头插法
pcur = new Node(date);
pcur->_pnext = _table[bucketnum];
_table[bucketnum] = pcur;
_size++;
return true;
}
bool insertequal(const T& date){}
bool eraseunique(const T& date) {
//1.寻找桶号
size_t bucketnum = hashfunc(date);
Node * pcur = _table[bucketnum];
Node * ppre = nullptr;
while (pcur) {
if (pcur->_date == date) {
//erase
if (ppre == nullptr) {
//删除的是第一个节点
_table[bucketnum] = pcur->_pnext;
}
else {
ppre->_pnext = pcur->_pnext;
}
delete pcur;
_size--;
return true;
}
ppre = pcur;
pcur = pcur->_pnext;
}
return false;
}
bool eraseequal(const T& date) {}
Node* find(const T& date)const {
//1.通过哈希函数计算桶号
size_t bucketnum = hashfunc(date);
//2.检测date元素在哈希桶中是否存在
Node *pcur = _table[bucketnum];
while (pcur) {
if (pcur->_date == date)
return pcur;
pcur = pcur->_pnext;
}
return nullptr;
}
size_t size() {
return _size;
}
bool empty()const {
return 0 == _size;
}
void print() {
for (size_t bucketnum = 0; bucketnum < _table.capacity();bucketnum++) {
std::cout << "_table[" << bucketnum << "]:";
Node* pcur = _table[bucketnum];
while (pcur) {
std::cout << pcur->_date << " ----> ";
pcur = pcur->_pnext;
}
std::cout << "\n";
}
}
private:
size_t hashfunc(const T& date) const{
return DF()(date) % _table.capacity();
}
//如果哈希桶存储元素个数和桶的个数相同
//原因:最佳状态:每个桶中存储一个元素
//每个桶中都有一个元素,就会哈希冲突。
//桶的利用率最大
void checkcapacity() {
size_t bucketCount = _table.capacity();
size_t flag = 1;
for (size_t bucketnum = 0; bucketnum < bucketCount; ++bucketnum) {
if (_table[bucketnum] == nullptr) {
flag = 0;
break;
}
}
if (flag==1) {
//扩容方式
//可以用闭散列的扩容方式,但是不太好,创建新结点,空间花销大。
//直接将旧哈希桶中元素结点,搬移到新哈希桶
hashbucket<int> newHt(bucketCount);
for (size_t bucketnum = 0; bucketnum < bucketCount; ++bucketnum)
{
Node* pcur = _table[bucketnum];
while (pcur)
{
// 将该节点从原哈希表中拆出来
_table[bucketnum] = pcur->_pnext;
// 将该节点插入到新哈希表中
size_t bucketNo = newHt.hashfunc(pcur->_date);
pcur->_pnext = newHt._table[bucketNo];
newHt._table[bucketNo] = pcur;
pcur = _table[bucketnum];
}
}
newHt._size = _size;
_table.swap(newHt._table);
}
}
private:
std::vector<Node*> _table;
size_t _size;
};
void testbucket() {
hashbucket<int> ht;
int array[] = { 1,2,3,4,5,6,7,8,9,6,5,0 };
for (auto e : array)
ht.insertunique(e);
ht.print();
ht.insertunique(44);
ht.insertunique(54);
ht.print();
ht.eraseunique(44);
ht.print();
}
- 3.寻找素数以及不同元素类型的实现头文件 commo.h
#pragma once
#include<string>
size_t Getnextprime(size_t capacity);
template<class T>
class DFDef {
public:
size_t operator()(const T& date) {
return date;
}
};
class DFstr {
public:
size_t operator()(const std::string& date) {
return BKDRHash(date.c_str());
}
private:
size_t BKDRHash(const char*str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch;
}
return hash;
}
};
- common.cpp
#include"common.h"
const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] = {
10ul,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
};
size_t Getnextprime(size_t capacity) {
for (auto e : primeList) {
if (e > capacity)
return e;
}
return primeList[PRIMECOUNT];
}
- 测试代码hashtes.cpp
#include<iostream>
#include"haxi.h"
#include"hasubucket.hpp"
int main() {
testbucket();
return 0;
}