一个通俗的例子是,为了查找电话簿中某人的号码,可以创建一个按照人名首字母顺序排列的表(即建立人名x到首字母F(x)的一个函数关系),在首字母为W的表中查找“王”姓的电话号码,显然比直接查找就要快得多。这里使用人名作为关键字,“取首字母”是这个例子中散列函数的函数法则F(),存放首字母的表对应散列表。关键字和函数法则理论上可以任意确定。
===============================================================================================
术语:
1.关键字:元素的存储部分,数据库的元素通过它进行存储,查找等操作(也称作散列关键字)
2,散列表元:散列数组的某个位置,其后跟着另外一个包含其元素的结构
3,散列函数:对关键字和散列表元提供映射的函数
4,完全散列函数:对关键字和整数提供一一映射的函数
=================================================================================================
以下是几种常用的散列函数:
1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,其中a和b为常数(这种散列函数叫做自身函数)
2. 数字分析法
3. 平方取中法
4. 折叠法
5. 随机数法
6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词
=================================================================================================
可以想象得出,散列函数的性能直接决定了散列表的性能,当散列函数把太少或太多关键字映射到单个散列表元上时,散列表的使用效率会大大降低。
如果我们使用一个完全散列 函数,在散列表中查找元素的速度最快。该函数保证所有可能的关键字都会分别映射到唯一的散列表元编号上。这就暗示了每个散列表元之多包含一个单一元素,而且散列表实际上使用关键字作为下标的数组。但是通常很难找到一个合适的完全的散列函数。当一个特定散列函数中多个关键字映射到一个散列表元编号,称之为“冲突”。当散列表函数无法改写完善时,处理”冲突“有两个选择:
(1)允许散列表元中放置多个元素,也就是每一个散列表元是一个链表。
(2)调整冲突,以下是几种常用的方法:
1,线性探测法:从冲突的散列表元进行线性查找,直到找到一个空表元为止。
2,再散列法:再进行一次散列,使用一个不同的散列函数,甚至可能用到其他不同的元素关键字。
3,溢出区法:为散列关键字预留出一块区域,这可能是另外一个散列表,或许会用到自己的散列函数。
=================================================================================================
在使用中我倾向于使用允许一个散列表元放置多个元素的方法,这时实际上散列表就是一个链表数组。如下图:
这样在插入(散列表元有序),删除,查找,实际复杂度都在O(1)与O(L)之间,L是散列表元中元素的最大数目
=================================================================================================
下面是我个人实现的 hash table
(1)采用链接法解决散列表元的碰撞
(2)散列函数采用除留余数法
#include <iostream> #include <cstdio> #include <memory.h> #include <string> #include <vector> #include <list> #include <cstdlib> #include <algorithm> using namespace std; /* * 构建一个student类,把其对象存放到hash table中 * 由于hash table要直接操作student的成员,所以全部为public *注意重载操作符==,因为student是自定义类型,所以在调用find算法时,要调用自定义类型的operator==()函数,比较对象是否相等。 * */ class student { public: student(int id, string name, unsigned int score); ~student(); bool operator==(const student& stu) const; int _id; string _name; unsigned int _score; }; student::student(int id, string name, unsigned int score) { _id=id; _name=name; _score=score; } student::~student() { } bool student::operator==(const student& stu) const { return (stu._id==_id && stu._name==_name && stu._score==_score); } /* *散列表是一个vector,大小初始化为10,注意STL的初始化语法,很蛋疼!!不能在定义的时候初始化!!! * 当散列表元有碰撞产生时(一个表元要存储多个数据),这里采用连接法解决碰撞,还有一个方法是完全散列法(open addressing),就是为每一个元素都分配一个唯一的散列表元 * 元素关键字到散列表元的映射靠散列函数实现,散列函数直接影响散列表的性能,十分重要! * vector<list<student> > array,list嵌套在vector中,我的理解是vector每一项保存一个list的表头,注意要给list初始化 */ class hash_table { public: explicit hash_table(int size = 10):array(size) { for(unsigned int i=0;i<array.size();++i) array[i].clear(); } ~hash_table(); void hash_insert(student& stu); bool hash_contain(const student& stu) const; void hash_delete(student stu); void print(); protected: int hash_function(int num) const; private: vector<list<student> > array; //int _size; }; /* hash_table::hash_table() { for(int i=0;i<10;++i) array[i].clear(); } */ hash_table::~hash_table() { } int hash_table::hash_function(int num) const { return (num%10); } void hash_table::hash_insert(student& stu) { int index = hash_function(stu._id); if(index>=10 || index<0) return; list<student> &list_tmp = array[index]; list_tmp.push_back(stu); } bool hash_table::hash_contain(const student& stu) const { int index = hash_function(stu._id); if(index>=10 || index<0) return false; const list<student> &list_tmp = array[index]; list<student>::const_iterator it = find(list_tmp.begin(), list_tmp.end(), stu); if(it != list_tmp.end()) return true; return false; } void hash_table::hash_delete(student stu) { int index = hash_function(stu._id); if(index>=10 || index<0) return; list<student> &list_tmp = array[index]; list<student>::iterator it = find(list_tmp.begin(), list_tmp.end(), stu); if(it != list_tmp.end()) list_tmp.erase(it); } void hash_table::print() { for(unsigned int i=0; i<array.size();++i) { list<student> list_tmp=array[i]; for(list<student>::const_iterator it =list_tmp.begin();it != list_tmp.end();++it) { cout<<"id: "<<(*it)._id<<" name: "<<(*it)._name<<" score: "<<(*it)._score<<endl; } } } /* *测试部分,顺序输入15个student对象 * 打印散列表元素 * 查找id为2的student * 删除后再打印 * **/ int main() { hash_table myHash; for(int i=0; i<15;i++) { char name[10]; sprintf(name, "student%d", i); student stu(i, name, 70+i); myHash.hash_insert(stu); } myHash.print(); bool ok1 = myHash.hash_contain(student(2, "student2", 72)); if(ok1) cout<<"student(2, student2, 72) exsits"<<endl; else cout<<"student(2, student2, 72)does not exsits"<<endl; myHash.hash_delete(student(2, "student2", 72)); cout<<"myHash.hash_delete(student(2, student2, 72))"<<endl; bool ok2 = myHash.hash_contain(student(2, "student2", 72)); if(ok2) cout<<"student(2, student2, 72) exsits"<<endl; else cout<<"student(2, student2, 72)does not exsits"<<endl; }
分析: 可以看到,由于id是关键字,散列函数是id对10取余,所以余数相同的结点保存在一起!!!