我们知道,由于二叉树的特性(完美情况下每次比较可以排除一半数据),对其进行查找算是比较快的了,时间复杂度为O(logN)。但是,是否存在支持时间复杂度为常数级别的查找的数据结构呢?答案是存在,那就是散列表(hash table,又叫哈希表)。散列表可以支持O(1)的插入,理想情况下可以支持O(1)的查找与删除。
散列表的基本思想很简单:
1.设计一个散列函数,其输入为数据的关键字,输出为散列值n(正整数),不同数据关键字必得出不同散列值n(即要求散列函数符合单射)
2.创建一个数组HashTable(即散列表),插入的数据存储在HashTable[n]中,n为数据的散列值且小于散列表的最大下标
这样一来,插入数据只需要计算出数据的散列值n,而后将数据存至HashTable[n]。查找数据则根据数据计算出散列值n,而后检查HashTable[n]是否存有数据即可,删除同理。这些操作都是O(1)。
但是稍加思索就会发现,上述思想是不可能在任意情况下都实现的:
一来,不可能任意情况下都有单射的散列函数,比如数据关键字为任意整数时,关键字-a与a该如何映射?
二来,即使散列函数是单射,散列表的大小也不可能总是保证大于所有可能的散列值,比如数据关键字为正整数,那么散列函数只需要令散列值等于数据关键字即可保证单射,但是如果数据总量为1000,而数据的可能最大值为10000000,难道我们创建一个大小为10000000的散列表吗?
也就是说,我们实际实现散列表时,必须面对这两个问题:
1.如何实现一个尽可能“接近”单射的散列函数
2.当不同数据关键字散列值相同时,如何处理这种冲突
第一个问题显然是因情而异的,只有给定了数据类型和一定的数据特性,才能写出对应的、好的散列函数。比如数据的key为随机正整数时,简单的散列函数是直接返回key%tableSize,这样做也没有多大问题。但是如果知道散列表的tableSize为100,且数据的key个位和十位必然为0,那么这样的散列函数就是不行的,必须修改。
也就是说,第一个问题是不存在普适性解法的,实现一个良好的散列函数本身又是另一件算法设计的事情,所以我们对于第一个问题不进行深入讨论。接下来的讨论假定这样的情形:输入的数据(关键字)为长度不超过20的字符串,且散列函数如下:
//简单的散列函数,将字符串中字符的ASCII码值相加,然后返回其与tableSize求余后的结果 unsigned int Hash(const char *target,unsigned int tableSize) { unsigned int HashVal = 0; while (*target != '