半个多月没学了,加班,私事,各种事。
链式哈希表用一个二维数组实现,一维存放桶索引,二维存放链表。
线性探测哈希表缺陷:
1 发生哈希冲突,靠近O(n)的时间复杂度,存储删除等操作变慢了;能不能不变慢呢?
2 多线程环境中,线性探测所用到的基于数组实现的哈希表只能给全局表用互斥锁来保证哈希表的原子操作,保证线程安全。(用了一把锁来锁数组),但是,链式哈希可以用分段锁,既保证了线程安全,又有一定的并发量,提高了效率。
链式哈希表的优化:
优化1:当链表长度大于8或者某个指定的值,把同里面的这个链表转换成为红黑树,时间复杂度从O(n)变为O(logn);
优化2:链式哈希表,每个桶都可以创建自己的互斥锁,不同桶中的链表操作可以并发执行。
#include <iostream>
#include <list>
#include <vector>
#include <functional>
using namespace std;
class HashTable
{
public:
HashTable(int size = primes_[0], double loadFactor = 0.75)
:useBucketNum_(0)
, loadFactor_(loadFactor)
, primeIdx_(0)
{
for (; primeIdx_ < PRIME_SIZE; primeIdx_++) // 将素数调整到素数表元素中最接近的位置。
{
if (primes_[primeIdx_] >= size)
break;
}
// 如果输入太大,直接最后一个素数
if (primeIdx_ == PRIME_SIZE)
{
primeIdx_--;
}
table_.resize(primes_[primeIdx_]); // 直接开辟primes_[primeIdx_]个空间
}
public:
void insert(int key)
{
// 判断扩容
double factor = useBucketNum_ * 1.0 / table_.size();
cout << "factor" << factor << endl;
if (factor > loadFactor_)
{
expand();
}
int idx = key % table_.size();
if (table_[idx].empty())
{
useBucketNum_++; // 一个桶只加一次
table_[idx].emplace_front(key); // 头插和尾插都可以
}
else // 如果桶中已经有元素,先搜索key元素是否已经存在
{
// 使用全局的::find() 泛型算法,而不是调用自己的成员函数方法find
auto it = ::find(table_[idx].begin(), table_[idx].end(), key);
if (it == table_[idx].end()) // 桶里不存在key时候才加
{
table_[idx].emplace_front(key); // 头插
}
else
{
cout << "已经存在key值" << endl;
}
}
}
// 删除元素
void erase(int key)
{
int idx = key % table_.size();
auto it = ::find(table_[idx].begin(), table_[idx].end(), key);
if (it != table_[idx].end()) // 如果找到了
{
table_[idx].erase(it);
if (table_[idx].empty()) // 如果删除完之后,该桶为空,useBucketNum_--;
{
useBucketNum_--;
}
}
}
// 搜索元素
bool find(int key)
{
int idx = key % table_.size();
auto it = ::find(table_[idx].begin(), table_[idx].end(), key);
return it != table_[idx].end();
}
private:
void expand()
{
if (primeIdx_ + 1 == PRIME_SIZE) // 如果当前已经是素数最大值,直接抛出异常
{
throw "hashtable can not expand antmore!";
}
primeIdx_++;
useBucketNum_ = 0;
vector<list<int>> oldTable; // oldTable存放当前数组的元素,当前的table_变成空
//
table_.swap(oldTable); // table_放入 oldTable中,将table_重新进行
table_.resize(primes_[primeIdx_]); // table_变为扩容后的大小
for (auto list : oldTable) // 遍历所有桶,如果某个桶为空则不执行下面的for
{
for (auto key : list) // 遍历桶的所有的元素
{
int idx = key % table_.size();
if (table_[idx].empty()) // 如果桶是空的,useBucketNum_++;
{ // 如果不为空,直接采用头插或尾插,插入到桶中。
useBucketNum_++;
}
table_[idx].emplace_front(key);
}
}
}
private:
vector<list<int>> table_; // 哈希表的数据结构:一维是数组,二维是链表。
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()
{
int data = 3;
HashTable thashtable;
thashtable.insert(11);
thashtable.insert(32);
thashtable.insert(12);
thashtable.insert(43);
cout << thashtable.find(43) << endl;
thashtable.erase(43);
cout << thashtable.find(43) << endl;
system("pause");
return 0;
}