散列表
散列表是一种查找技术,通俗一点说就是无需任何比较,直接通过关键码便能得到待查记录的存储位置。原理就必须在记录的存储位置和它的关键码之间建立一个确定的对应关系H,使得每个关键码key和惟一的一个存储位置H(key)相对应。在查找时,根据这个确定的对应关系找到给定值k的映射H(k),若查找集合中存在这个记录,则必定在H(k)的位置上。
这里的关键码可以看成你要查找的东西,比如整数23存储在哈希表的数组中,那么关键码就是整数23
也就是说我们仅需要这个关键码进行某种操作,就能得到它在哈希表数组内的下标,这样我们就无需遍历整个哈希表数组了。
散列表类定义
这里仅实现了线性探查法的搜索,插入,删除以及二次探查法的搜索,即FindPos2,二次探查法的插入就不编了,就是散列表的长度要符合4k+3的质数定义,以及里面的装载因子要小于0.5。
二次探查法相对于线性探查法,解决了数据堆积的问题,个人感觉主要是二次探查法每次插入进行的变化是+1,-1,+4,-4....都是平方变化,这样就让数据分散的比较均匀,用空间换取查找速度。
enum NodeInfo { Empty, Active, Delete };//记录节点的状态
template<typename K>
class HashTable
{
public:
HashTable(int d,int sz= DefaultSize);
~HashTable();
void PrintValue();
bool Search(const K& k);//查看键码k是否存在
bool Insert(const K& k);//插入键码k
bool Remove(const K& k);//删除键码k
private:
int FindPos1(const K& k)const;//寻找键k在哈希表的存储数组中对应的位置,使用线性探查法
int FindPos2(const K& k)const;//寻找键k在哈希表的存储数组中对应的位置,使用二次探查法
int curSize, maxSize;//curSize存储当前哈希表存储节点的数量,maxSize为哈希表最多存储的节点的数量
NodeInfo* info;//存储每个节点的状态数组,作为辅助数组
int divitor;//散列表的除数
K* value;//哈希表的存储节点的数组
};
线性探查法
需要注意的是寻找下一个桶的下标是通过j = (j + 1) % maxSize完成的。
//寻找键k在哈希表的存储数组中对应的位置,使用线性探测法
template<typename K>
int HashTable<K>::FindPos1(const K& k) const
{
int i = k % divitor;
if (info[i] == Empty) return -1;//如果info状态为空,那么该键码不存在哈希表中
int j = i;//j作为移动的指针
do
{
if (info[j] == Active && value[j] == k) return j;//如果节点转态为Active,且value数组在j处值为k
j = (j + 1) % maxSize;//位移
} while (j != i);
return -1;
}
二次探查法
需要注意,每次while循环仅移动一次,比如+1,-1,+4,-4,需要进行四次循环,并且在+1,-1中这两次循环中i是相同的(第二次使用的是save,保存了上一次i的数值)
//寻找键k在哈希表的存储数组中对应的位置,使用二次探查法
template<typename K>
int HashTable<K>::FindPos2(const K& k) const
{
int i = k % divitor;
int flag = 0;//控制加减号,由于每次进进行移动时+1,-1,+4,-4.....
int save, j = 0;//j代表每次递增进位
while (info[i] == Delete || info[i] == Active && value[i] != k)//每次循环flag变化一次
{
if (flag == 0)
{
save = i;//保存此时的i值,下次循环时时使用
j++;
i = (i + 2 * j - 1) % maxSize;//2*j-1,是+1变成+4需要增加增量,这里不使用平方,平方耗时
i = (i < 0) ? (i + maxSize) : i;//当i为0时,第一次循环时i会变为负值,将其变为正值
flag = 1;
}
else
{
i = (save - 2 * j + 1) % maxSize;
i = (i < 0) ? (i + maxSize) : i;
flag = 0;
}
}
if (info[i] == Empty) return -1;
else if(value[i] == k) return i;
}
全部代码
HashTable.h
#pragma once
#ifndef HASHTABLE_H
#define HASHTABLE_H
#include<iostream>
#include<cassert>
const int DefaultSize = 128;
using std::cout;
using std::endl;
enum NodeInfo { Empty, Active, Delete };//记录节点的状态
template<typename K>
class HashTable
{
public:
HashTable(int d,int sz= DefaultSize);
~HashTable();
void PrintValue();
bool Search(const K& k);//查看键码k是否存在
bool Insert(const K& k);//插入键码k
bool Remove(const K& k);//删除键码k
private:
int FindPos1(const K& k)const;//寻找键k在哈希表的存储数组中对应的位置,使用线性探查法
int FindPos2(const K& k)const;//寻找键k在哈希表的存储数组中对应的位置,使用二次探查法
int curSize, maxSize;//curSize存储当前哈希表存储节点的数量,maxSize为哈希表最多存储的节点的数量
NodeInfo* info;//存储每个节点的状态数组,作为辅助数组
int divitor;//散列表的除数
K* value;//哈希表的存储节点的数组
};
#endif // !HASHTABLE_H
template<typename K>
HashTable<K>::HashTable(int d, int sz)
{
divitor = d;
maxSize = sz;
curSize = 0;
value = new K[maxSize];
info = new NodeInfo[maxSize];
assert(value && info);
for (int i = 0; i < maxSize; i++)//所有节点状态都设为空
info[i] = Empty;
}
template<typename K>
HashTable<K>::~HashTable()
{
if (value) delete[] value;
if (info) delete[] info;
}
template<typename K>
void HashTable<K>::PrintValue()
{
for (int i = 0; i < maxSize; i++)
{
if (info[i] == Active) cout << value[i] << " ";
else cout << 0 << " ";
}
cout << endl;
}
//查看键码k是否存在
template<typename K>
bool HashTable<K>::Search(const K& k)
{
if (FindPos1(k) >= 0)
return true;
return false;
}
template<typename K>
bool HashTable<K>::Insert(const K& k)
{
if (FindPos1(k) >= 0) return false;
int i = k % divitor;
int j = i;//j作为移动的指针
do
{
if (info[j] == Empty) {//当节点状态为空时,进行插入,否则j将一直移动下去
value[j] = k;
info[j] = Active;
curSize++;
return true;
}
else
j = (j + 1) % maxSize;
} while (j != i);
return false;
}
template<typename K>
bool HashTable<K>::Remove(const K& k)
{
int x = FindPos1(k);
if (x < 0) return false;
else {
info[x] = Delete;
curSize--;
return true;
}
}
//寻找键k在哈希表的存储数组中对应的位置,使用线性探测法
template<typename K>
int HashTable<K>::FindPos1(const K& k) const
{
int i = k % divitor;
if (info[i] == Empty) return -1;//如果info状态为空,那么该键码不存在哈希表中
int j = i;//j作为移动的指针
do
{
if (info[j] == Active && value[j] == k) return j;//如果节点转态为Active,且value数组在j处值为k
j = (j + 1) % maxSize;//位移
} while (j != i);
return -1;
}
//寻找键k在哈希表的存储数组中对应的位置,使用二次探查法
template<typename K>
int HashTable<K>::FindPos2(const K& k) const
{
int i = k % divitor;
int flag = 0;//控制加减号,由于每次进进行移动时+1,-1,+4,-4.....
int save, j = 0;//j代表每次递增进位
while (info[i] == Delete || info[i] == Active && value[i] != k)//每次循环flag变化一次
{
if (flag == 0)
{
save = i;//保存此时的i值,下次循环时时使用
j++;
i = (i + 2 * j - 1) % maxSize;//2*j-1,是+1变成+4需要增加增量,这里不使用平方,平方耗时
i = (i < 0) ? (i + maxSize) : i;//当i为0时,第一次循环时i会变为负值,将其变为正值
flag = 1;
}
else
{
i = (save - 2 * j + 1) % maxSize;
i = (i < 0) ? (i + maxSize) : i;
flag = 0;
}
}
if (info[i] == Empty) return -1;
else if(value[i] == k) return i;
}
main.cpp
#include"HashTable.h"
int main()
{
HashTable<int> myhash(11, 12);
int a[] = { 37,25,14,36,49,68,57,11 };
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
myhash.Insert(a[i]);
myhash.PrintValue();
return 1;
}