基本概念
查找树ADT允许对一组元素进行各种操作,而今次总结的散列表(hash table)ADT,不过它只支持二叉查找树所允许的一部分操作,散列表的实现常常叫做散列(hashing),以常数平均时间执行插入、删除和查找的技术。
理想的散列表数据结构只不过是一个包含有关键字的具有固定大小的数组,把表的大小叫做TableSize,并将其理解为散列数据结构的一部分而不仅仅是浮动于全局的某个变量,每个关键字被映射到从0到TableSize-1这个范围中的某个数,并且被放到适当的单元中,这个映射就叫做散列函数(hash function),理想的情况下它应该运算简单并且应该保证任何两个不同的关键字映射到不同的单元。当然理想的情况是不可能,单元的数目是有限的,而关键字实际上是用不完的,因此我们要寻找一个散列函数,该函数要在单元之间均匀的分配关键字。这就是散列的基本想法,剩下的问题是要选择一个函数,决定当两个关键字散列到同一个值得时候(称为冲突(collision))应该做什么以及如何确定散列表的大小。
散列函数
一种选择方法是把字符串中字符的ASCII码值加起来:
unsigned int Hash(const char *Key, int TableSize)
{
unsigned int HashVal = 0;
while (*Key != ‘\0’)
HashVal += *Key++;
return HashVal % TableSize;
}
还有一种方法,这个散列函数涉及到关键字中的所有字符,并且一般可以分布的更好,借助于多项式的计算来实现的:
unsigned int Hash(const char *Key, int TableSize)
{
unsigned int HashVal = 0;
while (*Key != ‘\0’)
HashVal = (HashVal << 5) + *Key++;
return HashVal % TableSize;
}
上面描述的散列函数就表的分布而言未必是最好的,但是确实具有及其简单的优点。剩下的主要编程细节是解决冲突的消除问题,如果当一个元素被插入时另一个元素已经存在(散列值相同),那么就产生一个冲突,这个冲突的解决有两种简单的方法:分离链接法和开放定址法。
分离链接法
其做法是将散列到同一个值的所有元素保留到一个表中。
struct ListNode
{
ElementType Element;
Postion Next;
};
struct HashTable
{
int TableSize;
ListNode **TheLists;
};
- 初始化函数用到与栈的数组实现中相同的想法,给一个散列表结构分配空间,如果空间允许,则H将指向一个结构,该结构包含一个整数和指向一个表的指针,设置表的大小为一素数。
HashTable *InitializeTable(int TableSize)
{
HashTable *H;
int i;
if (TableSize < MinTableSize)
{
std::cout << “Table size too small” << std::endl;
return NULL;
}
H = new HashTable;
if (H == NULL)
std::cout << “Out of space” << std::endl;
H->TableSize = NextPrime(TableSize);
H->TheLists = new (ListNode*) * H->TableSize;
if (H->TheLists == NULL)
std::cout << “Out of space” << std::endl;
for (i = 0; i < H->TableSize; i++)
{
H->TheLists[i] = new ListNode;
if (H->TheLists[i] == NULL)
std::cout << “Out of Space” << std::endl;
else
H->TheLists[i]->Next = NULL;
}
return H;
}
- Find(Key, H)函数将返回一个指针,该指针指向包含Key的那个单元。
ListNode *Find(ElementType Key, HashTable *H)
{
ListNode *P;
ListNode *L;
L = H->TheLists[Hash(Key, H->TableSize)];
P = L->Next;
while (P != NULL && P->Element != Key)
P = P->Next;
return P;
}
- 对于插入操作,如果要插入的项已经存在,那么就什么也不做,否则把它放到表的前端。
void Insert(ElementType Key, HashTable *H)
{
ListNode *Pos, NewCell;
ListNode *L;
Pos = Find(Key, H);
if (Pos == NULL)
{
NewCell = new ListNode;
if (NewCell == NULL)
std::cout << “Out of space” << std::endl;
else
{
L = H->TheLists[Hash(Key, H->TableSzie)];
NewCell->Next = L->Next;
NewCell->Element = Key;
L->Next = NewCell;
}
}
}