目录
1.什么是哈希表
哈希表就是散列表,是将输入关键字,通过散列函数计算得到哈希值,用这个哈希值去快速查找或定位的数据结构。
散列(哈希)函数(hash)就是针对输入的关键字,不管是用于比较的数字还是字符串,设计的一个函数或算法。该函数或算法可以通过一种计算,将输入映射为一个确定的数字,然后将这个数字作为数组的索引,从而在O(1)的时间内快速找到目标。例如,假设输入为ABC,哈希表为静态数组A[100],通过哈希函数hash(ABC) = 8,得到输入ABC的哈希值为8,那么A[8]就是要查找的目标或者存放位置。(存还是查找看情况)
哈希表好用,但是哈希函数不好构造,主要是因为针对不同的输入,通过一般哈希函数计算出来的哈希值他有可能是一样的,也就是所谓的哈希冲突。一个好的哈希函数应该考虑两个因素:a.计算简单,以便提高转换速度 b.关键词对应的地址空间分布均匀,以减少冲突。程序中设计的关键字无非字符串和数字,针对这两种不同的关键字,有不同的哈希计算策略。
一.数字关键字哈希值计算方法
-
直接定址法:取关键字的某个线性函数值作为散列地址,比如hash(key) = a*key+b,其中a,b是常数。
-
除留余数法:hash(key) = key%p。就是关键字除以一个数,用除不尽的余数作为哈希值。但是像2,4这样的数作为除数也有很大问题,8%2 = 0,16%2 = 0,凡是能被2除尽的,余数都为0。因此对除数的选择需要一点策略,书上说一般采用素数。
-
数字分析法 :分析数字关键字在各个位上的变化情况,取比较随机的作为散列地址,比如去手机号码最后四位作为散列地址。
二.字符关键字哈希值计算方法
既然哈希冲突是个普遍现象,那就坦然接收,想办法解决冲突。书上说有开放地址法,线性探测法,平方探测法,分离链式法等。
2.哈希表分离链式法
我比较倾向于使用分离链式法来解决哈希冲突,比较好理解。链式法数据结构定义如下:
typedef int Index; /* 散列地址类型 */
/******** 单链表的定义 ********/
typedef struct LNode *PtrToLNode;
struct LNode {
ElementType Data;
PtrToLNode Next;
};
typedef PtrToLNode List;
/******** 单链表的定义 ********/
typedef struct TblNode *HashTable; /* 散列表类型 */
struct TblNode { /* 散列表结点定义 */
int TableSize; /* 表的最大长度 */
List Heads; /* 指向链表头结点的数组 */
};
可以看到,哈希表就是链表数组,数组的每一个元素都是单向链表头指针。我们根据哈希函数计算得到散列值,用这个散列值作为数组的下标来看当前元素是否为空,不为空说明有冲突,就直接追加到链表好了。有了哈希函数和哈希表结构,就可以利用哈希表做插入和查找了。
3.怎么用哈希表
常用对哈希表的操作包括,创建哈希表,销毁哈希表,哈希查找,哈希插入表等。用以下代码实例说明。
//创建哈希表
HashTable CreateTable( int TableSize )
{
HashTable H;
int i;
H = (HashTable)malloc(sizeof(struct TblNode));
/* 保证散列表最大长度是素数,具体见代码5.3 */
H->TableSize = NextPrime(TableSize);
/* 以下分配链表头结点数组 */
H->Heads = (List)malloc(H->TableSize*sizeof(struct LNode));
/* 初始化表头结点 */
for( i=0; i<H->TableSize; i++ ) {
H->Heads[i].Data[0] = '\0';
H->Heads[i].Next = NULL;
}
return H;
}
//哈希查找,这里哈希函数可以用简单的除留余数法来实现
Position Find( HashTable H, ElementType Key )
{
Position P;
Index Pos;
Pos = Hash( Key, H->TableSize ); /* 初始散列位置 */
P = H->Heads[Pos].Next; /* 从该链表的第1个结点开始 */
/* 当未到表尾,并且Key未找到时 */
while( P && strcmp(P->Data, Key) )
P = P->Next;
return P; /* 此时P或者指向找到的结点,或者为NULL */
}
//根据关键字查找
bool Insert( HashTable H, ElementType Key )
{
Position P, NewCell;
Index Pos;
P = Find( H, Key );
if ( !P ) { /* 关键词未找到,可以插入 */
NewCell = (Position)malloc(sizeof(struct LNode));
strcpy(NewCell->Data, Key);
Pos = Hash( Key, H->TableSize ); /* 初始散列位置 */
/* 将NewCell插入为H->Heads[Pos]链表的第1个结点 */
NewCell->Next = H->Heads[Pos].Next;
H->Heads[Pos].Next = NewCell;
return true;
}
else { /* 关键词已存在 */
printf("键值已存在");
return false;
}
}
//销毁哈希表
void DestroyTable( HashTable H )
{
int i;
Position P, Tmp;
/* 释放每个链表的结点 */
for( i=0; i<H->TableSize; i++ ) {
P = H->Heads[i].Next;
while( P ) {
Tmp = P->Next;
free( P );
P = Tmp;
}
}
free( H->Heads ); /* 释放头结点数组 */
free( H ); /* 释放散列表结点 */
}
4.说明
文档全部截图和代码均来自中国大学mooc中,浙江大学数据和算法课程。