无论是顺序查找还是二分查找(包括二叉树),查找或插入的时间复杂度总会与数据的总数N有关,而散列表可以将查找和插入操作降低到常数级别。
最简单的一种常数级别的符号表,就是直接将数据的键值作为数组的索引,通过键值就可以立即访问到数据,时间复杂度为1.但是这种方法有一个缺点就是键值的分布并不是平均的,而且跨度可能会很大,这样就会导致数组中很多空间被浪费了。
而散列表就是解决了这个缺点,将键值映射到均匀分布的一定范围内的索引值。而在映射的过程中,由于键值的不均匀分布,必然会出现不同键值映射到同一索引的情况(即碰撞),所以还要进行碰撞处理,常用的有拉链式和线性探测等。
1.拉链式
顾名思义,就是把产生碰撞的数据按顺序排成链表,先通过映射找到所在链表,然后再在链表中查找数据。
除了映射处理,主要的时间都是花在链表查找上,所以链表越短查找越快,但是空间占用也越大,所以根据实际情况作平衡处理。另一方面,链表的平均长度=数据量N/链表数量M,因此应当尽量保证每条链表的长度差不多,那么就要使用更加均匀的映射函数。
我这里使用的是C++自带的hash函数,再按链表数M取余。即:
std::hash<int> h;
int h_val=h(key)%_chainNum;
拉链式散列表的声明:
class SeparateChainingHashST
{
public:
SeparateChainingHashST(int chainNum);//指定链表数目
~SeparateChainingHashST();
void put(int key, int val);
ChainNode* get(int key);
void showChains();//所有链表打印出来
private:
void deleteAllNodes(ChainNode* head);//删除链表头为head的所有节点
inline unsigned int hash(int key);//计算哈希值(小于链表数)
private:
int _chainNum;
NodePoniter *_chainHeads; //存放链表头的数组
std::hash<int> _h;//用来计算hash码
};
具体实现:
SeparateChainingHashST::SeparateChainingHashST(int chainNum)
{
_chainNum=chainNum;
_chainHeads=new NodePoniter[chainNum];//新建一个数组来存放链表的头
for(int i=0;i<chainNum;i++)//将所有链表头初始化为空指针
_chainHeads[i]=nullptr;
}
unsigned int SeparateChainingHashST::hash(int key)
{
// std::hash<int> h;
return _h(key)%_chainNum;
}
void SeparateChainingHashST::put(in