哈希表-线性探测法理论
线性探测法的理论我们在上一篇博客已经阐述了。
现在我们来看看线性探测法的增删查的代码思想:
哈希表的增加元素:
注意:往后遍历寻找空闲位置的时候,要注意是环形遍历哦!不然访问数组就越界了。
我们还要注意:在添加元素,发生位置被占用,即发生哈希冲突后,在向后遍历寻找空闲位置的时候,我们要知道,这个空闲的位置是有两种情况的:1、这个位置一直是空的,没放过元素。2、这个位置是空的,以前放过元素,后来被删除了。
哈希表的查询操作:
当用哈希函数计算得出的下标值是3,然后去访问数组,查询时,发现该值不等于要查询的元素的值val,说明当时放val的时候发生了哈希冲突,这时候就要向后遍历了,访问4下标的时候发现这个位置是空的,如果这个位置一直是空的,则就不用继续向后找了,val不存在!因为是线性探测法,所以当时val如果要放的时候肯定是要放在这里的。但是如果这个位置是空的,但是之前放过元素,后来被删除了,这个位置之前存放了元素,然后val插入的时候,就插到后面的空闲的位置了,所以此时我们还要继续往后遍历寻找val值。
所以我们需要定义一个Bucket节点来表示每一个元素的所有的内容。
//桶的状态
enum State
{
STATE_UNUSE, //从未使用过的桶
STATE_USING, //正在使用的桶 放着是一个有效的元素,没有被删过
STATE_DEL, //元素被删除了的桶,认为桶里的元素无效了
};
//我们删除桶里的元素,并不是真正把值删除掉,而是把桶的状态置为STATE_DEL就认为桶里的元素无效了
//桶的类型
struct Bucket
{
Bucket(int key = 0, State state = STATE_UNUSE)
: key_(key)
, state_(state)
{}
int key_; //存储的数据
State state_; //桶的当前状态
};
哈希表删除的操作:
哈希表-线性探测法代码实现
#include <iostream>
using namespace std;
//桶的状态
enum State
{
STATE_UNUSE, //从未使用过的桶
STATE_USING, //正在使用的桶 放着是一个有效的元素,没有被删过
STATE_DEL, //元素被删除了的桶,认为桶里的元素无效了
};
//我们删除桶里的元素,并不是真正把值删除掉,而是把桶的状态置为STATE_DEL就认为桶里的元素无效了
//桶的类型
struct Bucket
{
Bucket(int key = 0, State state = STATE_UNUSE)
: key_(key)
, state_(state)
{}
int key_; //存储的数据
State state_; //桶的当前状态
};
//线性探测哈希表类型
class HashTable
{
public:
HashTable(int size = primes_[0], double loadFactor = 0.75)//构造函数
: useBucketNum_(0)
, loadFactor_(loadFactor)
, primeIdx_(0)
{
//把用户传入的size调整到最近的比较大的素数上
if (size != primes_[0])
{
for (; primeIdx_ < PRIME_SIZE; primeIdx_++)
{
if (primes_[primeIdx_] >= size)
break;
}
//用户传入的size值过大,已经超过最后一个素数,调整到最后一个素数
if (primeIdx_ == PRIME_SIZE)
{
primeIdx_--;
}
}
tableSize_ = primes_[primeIdx_];
table_ = new Bucket[tableSize_];//开辟构造,调用Bucket的默认构造函数初始化
}
~HashTable()//析构函数
{
delete[]table_;
table_ = nullptr;
}
public:
//插入元素
bool insert(int key)
{
//考虑扩容
double factor = useBucketNum_*1.0 / tableSize_;
cout << "factor:" << factor << endl;
if (factor > loadFactor_)
{
//哈希表开始扩容
expand();
}
int idx = key % tableSize_;//计算下标
int i = idx;
do
{
if (table_[i].state_ != STATE_USING)
{
table_[i].state_ = STATE_USING;
table_[i].key_ = key;
useBucketNum_++;
return true;//O(1)
}
i = (i + 1) % tableSize_;//环形路径
} while (i != idx);//O(n)
//最多跑一圈,而且不可能跑一圈,因为哈希表是不可能放满的,超过加载因子就扩容
//do while 是先执行一次,再判断循环
return false;
}
//删除元素,只是把当前状态修改一下而已
bool erase(int key)
{
int idx = key % tableSize_;//计算出哈希值
int i = idx;
do
{
if (table_[i].state_ == STATE_USING && table_[i].key_ == key)
{
table_[i].state_ = STATE_DEL;
useBucketNum_--;
}
i = (i + 1) % tableSize_;
} while (table_[i].state_ != STATE_UNUSE && i != idx);
return true;
}
//查询 count(key)
bool find(int key)
{
int idx = key % tableSize_;
int i = idx;
do
{
if (table_[i].state_ == STATE_USING && table_[i].key_ == key)
{
return true;
}
i = (i + 1) % tableSize_;
} while (table_[i].state_ != STATE_UNUSE && i != idx);
return false;
}
private:
//扩容操作
void expand()
{
++primeIdx_;
if (primeIdx_ == PRIME_SIZE)//越界了
{
throw "HashTable is too large, can not expand anymore!";
}
Bucket* newTable = new Bucket[primes_[primeIdx_]];
for (int i = 0; i < tableSize_; i++)
{
if (table_[i].state_ == STATE_USING)//旧表有效的数据,重新哈希放到扩容后的新表
{
int idx = table_[i].key_ % primes_[primeIdx_];
int k = idx;
do
{
if (newTable[k].state_ != STATE_USING)//空闲的
{
newTable[k].state_ = STATE_USING;
newTable[k].key_ = table_[i].key_;
break;
}
k = (k + 1) % primes_[primeIdx_];
} while (k != idx);
}
}
delete[]table_;
table_ = newTable;
tableSize_ = primes_[primeIdx_];
}
private:
Bucket* table_;//指向动态开辟的哈希表,不要使用vector,因为它是二倍自动扩容,自增长
//要用vector也可以,因为哈希表有装载因子,达到0.75,哈希表进行扩容
int tableSize_;//哈希表当前的长度
int useBucketNum_;//已经使用的桶的个数
double loadFactor_;//哈希表的装载因子
static const int PRIME_SIZE = 10;//素数表的大小
static int primes_[PRIME_SIZE];//素数表
int primeIdx_;//当前使用的素数在素数表中的下标值
};
int HashTable::primes_[PRIME_SIZE] = { 3, 7, 23, 47, 97, 251, 443, 911, 1471, 42773 };//素数表的定义
//素数表的最后几个元素代表开辟哈希表数组堆空间的最大的一些内存大小,再大的话堆内存就开辟失败了
//所有哈希表的素数表都是一样的,可以定义成静态的
int main()
{
HashTable htable;
htable.insert(21);
htable.insert(32);
htable.insert(14);
htable.insert(15);
htable.insert(22);
cout << htable.find(21) << endl;
htable.erase(21);
cout << htable.find(21) << endl;
}