哈希表

哈希表

一种用于以常数平均时间执行插入、删除和查找操作的数据结构。

但是是无序的

一般想法

  • 通常为一个包含关键字的具有固定大小的数组
  • 每个关键字通过散列函数映射到数组中
  • 冲突:两个关键字映射到同一个值

    散列函数

    简单的散列函数

不均匀,不够好

1
2
3
4
5
6
7
8
9
10
11
typedef unsigned int Index ;

Index Hash(const char *key, int Tablesize)
{
unsigned int HashVal = 0;

while(*key != '\0')
HashVal += *key++ ;

return HashVal % Tablesize ;
}

一个好的散列函数

1
2
3
4
5
6
7
8
Index Hash(const char *key, int TableSize)
{
unsigned int HashVal = 0 ;
while(*key != '\0')
HashVal += (HashVal << 5) + *key++ ;

return HashVal %TableSize ;
}

解决冲突

分离链接法


> 将散列到同一个值的所有元素保存在一个表中

类型声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct ListNode ;
typedef struct ListNode *Position ;
struct Hashbl ;
typedef struct HashTbl *HashTable ;

HashTable IntializeTable(int TableSize) ;
void DestroyTable(HashTable H) ;
Position Find(ElemenetType Key, HashTable H) ;
Void Insert(ElementType Key, HashTable H) ;
ElementType Retireve(Position P) ;

struct ListNode
{
ElementType Element ;
Position Next ;
} ;

typedef Position List ;

strcut HashTbl
{
int TableSize ;
List *TheLists ; //实则为指针数组
}

哈希表的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HashTable IntializeTable(int TableSize)
{
HashTable H ;

TableSize = NextPrime(TableSize) ;//将表的大小设为它下一个素数
H->TheList = malloc(sizeof (List) * H->TableSize) ;
if(H->TheList == NULL)
FatalError("内存不足") ;
for(int i = 0,i < H->TableSize; i++)
{
H->TheLists[i] = malloc(struct ListNode) ;
if(H->TheLists[i] == NULL)
FatalError("内存不足") ;
else
H->TheLists[i]->Next = NULL ;
}

return H ;
}

Find函数

1
2
3
4
5
6
7
8
9
10
11
12
Position Find(ElementType Key, HashTable)
{
Position P ;
List L ;
L = H->TheLists[Hash(Key,H->TableSize)];
P = L->Next ;
while(P != NULL && P->Element != Key) //如果关键字为字符串,可能用到strcmp函数
P = P->Next ;

return P ;

}

Insert函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Insert(ElementType Key, HashTable H)
{
Position P ,TmpCell;
List L ;
TmpCell = malloc(ListNode) ;
if(TmpCell == NULL)
FatalError("内存不足") ;
else
{
L = H->TheLists[Hash(Key,H->TableSize)] ;
P = L->Next ;
TmpCell->Element = Key ;
TmpCell->Next = P ;
L->Next = TmpCell ;
free(TmpCell) ;
}
}

开放定址法

当发生冲突时,重新选择地址

1
Hi(X) = (Hash(X) + F(i)) mod TableSize ;
  • 线性探测法:会发生一次聚集
  • 平方探测法:效果较好
1
F(i) = i^2

类型声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef unsigned int Index ;
typedef Index Position ;

struct Hashbl ;
typedef struct HashTbl *HashTable ;

HashTable IntializeTable(int TableSize) ;
void DestroyTable(HashTable H) ;
Position Find(ElemenetType Key, HashTable H) ;
Void Insert(ElementType Key, HashTable H) ;
ElementType Retireve(Position P) ;

enum KindOfEntry{Legitimate,Empty,Deleted} ;

struct HashEntry
{
ElementType Element ;
enum KingOfEntry Info ;
} ;

typedef struct HashEntry Cell ;

strcut HashTbl
{
int TableSize ;
Cell *TheCells ; //直接用结构数组
}

Find函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Position Find(ElementType Key,HashTable H)
{
Position P ;
int Num = 0;
P = H->TheCells[Hash(Key,H->TableSize)] ;
while(H->TheCells[P].Info != Empty && H->TheCells[P].Element != Key) //如果关键字为字符串需要使用strcmp函数
{
P += 2 * ++Num - 1 ;
if(P >= H->TableSize)
P -= H->TableSize ;
}

return P ;
}

Insert函数

1
2
3
4
5
6
7
8
9
10
void Insert(ElementType Key, HashTable H)
{
Position P ;
P = Find(Key,H) ;
if(H->TheCells[P] != Legitimate)
{
H->TheCells[P].Info = Legitimate ;
H->TheCells[P].Element = Key ;//如果为字符串,则可能使用strcpy函数
}
}

总结

  • 哈希表以常数平均时间执行Insert和Find操作是重要特点,但它是无序的
  • 在解决冲突时使用不同的方法应该注意它的装填因子导致的效率问题
  • 应用:
    1. 编译器使用哈希表追踪代码中声明的变量
    2. 在图论当中每个节点应当由实际的名字
    3. 游戏编程中的变化表
    4. 在线拼写检验