哈希线性探测法的原理:增加元素,查询元素和删除元素。
增加元素:
通过哈希函数计算数据存放的位置。
如果该位置空闲,直接存储元素,完成;
如果该位置被占用,从当前位置向后找空闲的位置,存放该元素。
查询元素:
通过哈希函数计算数据存放的位置,从该位置取值。
如果该值==要查询的元素值,找到了!
如果该值 != 要查询的元素值(之前往这个位置放元素时,发生哈希冲突了),继续往后遍历找该元素。
判断空的两种情况:
情况1:这个位置一直是空的,没放过元素,那么找到该后置后,就不需要继续往后找了。
情况2:这个位置是空的,以前放过元素,后来被删除了。这种情况下,在查找是要继续往后查找。
所以,表中的每个节点定义为一个结构体:
// 桶的状态
enum State
{
STATE_USING, // 从未使用过的桶
STATE_UNUSED, // 正在使用的桶
STATE_DEL // 元素被删除了的桶
};
// 桶的类型
struct Bucket
{
Bucket(int key = 0,State state= STATE_UNUSED)
: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_];
}
~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_;
判断
//if (table_[idx].state_ != STATE_USING)
//{
// table_[idx].state_ = STATE_USING;
// table_[idx].key_ = key;
// return true;
//}
//for (int i = (idx + 1) % tableSize_; i != idx; i=(i+1)%tableSize_)
//{
// // 在遍历过程中,如果某个位置空闲,则插入
// if (table_[i].state_ != STATE_USING)
// {
// table_[i].state_ = STATE_USING;
// table_[i].key_ = key;
// return true;
// }
//}
// 上面代码优化
int i = idx;
do // 先判断再循环时候,就用do while
{
// 在遍历过程中,如果某个位置空闲,则直接插入
if (table_[i].state_ != STATE_USING)
{
table_[i].state_ = STATE_USING;
table_[i].key_ = key;
useBucketNum_++;
return true;
}
// 否则,i 等于 i 加1,寻找下一个插入的节点。
i = (i + 1) % tableSize_;
} while ( i != idx);
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_UNUSED && i != idx);
// 删除时候结束条件:往后寻找,直到后边元素状态等于UNUSED截止,并且不等于原始值存放的。
return true;
}
// 查询
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_UNUSED && i != idx);
return false;
}
private:
void expand()
{
primeIdx_++;
if (primeIdx_ == PRIME_SIZE)
{
throw "HashTable is too large! can not expand!";
}
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);
}
}
}
private:
Bucket * table_; // 指向动态开辟的哈希表
int tableSize_; // 哈希表当前空间的长度
int useBucketNum_; // 已经使用的桶的个数
double loadFactor_; // 哈希表的装载因子
static const int PRIME_SIZE = 10; // 素数表大小,C++11后,可在类外直接初始化
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(32) << endl;
thashtable.erase(32);
cout << thashtable.find(32) << endl;
system("pause");
return 0;
}