【数据结构】哈希表--链地址法 思路 插入新元素 头文件Ha

小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动

大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦。

不过这篇文章呢,还是想跟大家聊一聊数据结构与算法,学校也是大二上才开设了数据结构这门课,希望可以一边学习数据结构一边积累后台项目开发经验。

不知不觉,我们已经接触了两三个数据结构了:链表、栈和队列,但有没有发现,前边的几个数据结构在查找性能方面其实并不是很好?为了进一步改进查找的性能,我们又要学习另一个全新的数据结构–哈希表

思路

  • 数组+链表来实现

Java8,当哈希冲突达到一定程度后,每一个位置从链表转成红黑树

数组

  • 一个指针数组,里边存放着元素的地址

链表

  • 我们设计出来的哈希函数,难免会出现冲突(即A和B都找到了H.rcd[i]这个位置),链地址法解决的思路是,让他们存在于这个位置上的一条同义词链表上(所谓同义词就是指他们的哈希地址一样)

开放地址法比起来,当发生哈希冲突时,他不是去改变哈希地址的值,而是”将错就错”,让他们共存在同一个哈希地址上的链表上的不同位置。

插入新元素

先调用哈希函数找到val要所在的key

  • 若key有值,则遍历数组[key]上的同义词链表,验证是否已存在该val(哈希表要求不能重复)
    • 若存在,则return UNSUCCESS; //插入失败
      • 若不存在,则让该val指向原来的表头,同时成为新表头
  • 若key没值,则直接让数组[key]=val就好了

当然,上边都是伪代码,具体的实现还有很多细节需要注意

  • 而且上边的查找我们还可以单独封装成一个函数

头文件HashTable.h

1
2
3
4
5
6
c复制代码typedef long KeyType;

//Node装载的结构体定义,里边可能有多种数据类型的元素.这里假设只有keyType类型,即long类型
typedef struct{
KeyType key;
} RcdType;

头文件CHashTable.h

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
28
29
c复制代码#include "Common.h"
#include "HashTable.h"

typedef struct Node {
RcdType r;
struct Node *next;
} Node;

typedef struct {
//指针数组
Node **rcd;
// 哈希表容量
int size;
// 当前表中含有的记录个数
int count;
// 函数指针变量,用于选取的哈希函数
int (*hash)(KeyType key, int);
} CHashTable;

// 初始化哈希表
Status InitHash(CHashTable &H, int size, int (*hash)(KeyType,int));
// 销毁哈希表
Status DestroyHash(CHashTable &H);
// 查找
Node* SearchHash(CHashTable H, KeyType key);
// 插入
Status InsertHash(CHashTable &H, RcdType e);
// 删除
Status deleteHash(CHashTable &H, KeyType key, RcdType &e);

源文件CHashTable.cpp

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
c复制代码#include "CHashTable.h"

//初始化哈希表
Status InitHash(CHashTable &H, int size, int (*hash)(KeyType,int)) {
int i;
H.rcd = (Node**)malloc(sizeof(Node *)*size);
if(NULL==H.rcd) return OVERFLOW;
for (i = 0; i < size; i++) {
H.rcd[i] = NULL;
}
H.size = size;
H.hash = hash;
H.count = 0;
return OK;
}

// 销毁哈希表
Status DestroyHash(CHashTable& H) {
H.size = 0;
H.count = 0;
free(H.rcd);
free(H.hash);
//若销毁失败
if (H.rcd != NULL || H.hash != NULL) {
return UNSUCCESS;
}
return SUCCESS;
}

//查找
Node* SearchHash(CHashTable H, KeyType key) {
//找到在数组中的位置
int p = H.hash(key, H.size);
Node* np;
//在该位置上的同义词链表上搜索
for (np = H.rcd[p]; np != NULL; np = np->next) {
if (np->r.key == key) {
return np;
}
}
return NULL;
}

//插入记录e
Status InsertHash(CHashTable &H, RcdType e){
int p;
Node *np;
// 查找不成功时插入到表头
if((np = SearchHash(H, e.key))==NULL) {
p = H.hash(e.key, H.size);
np = (Node*)malloc(sizeof(Node));
if(np==NULL)return OVERFLOW;
np->r = e;
//先指向表头
np->next = H.rcd[p];
//再成为表头
H.rcd[p] = np;
H.count++;
return OK;
}
else
return ERROR;
}

//删除指定元素,并返回到e
Status deleteHash(CHashTable &H,KeyType key,RcdType &e) {
//找到所在位置
Node* np = SearchHash(H, key);
//若查到不到
if (NULL == np) {
return UNSUCCESS;
}
//若找到了
else
{
//找到在数组中的位置
int p = H.hash(key, H.size);
//当前节点
Node* pNow;
//返回给e
e = np->r;
//先特判是否是第一个
if (H.rcd[p] == np) {
//改变新表头
H.rcd[p] = np->next;
}
//若不是第一个
else
{
//在该位置上的同义词链表上搜索
for (pNow = H.rcd[p]; pNow != NULL && pNow->next != NULL; pNow = pNow->next) {
//若找到了Search返回结果的前驱
if (pNow->next == np) {
//让当前节点指向删除节点的下一个
pNow->next = np->next;
}
}
}
/*
注意释放
释放后np如果没有置空或者再赋值,就会变成野指针了
防止再被用到,手动置空一下
*/
free(np);
np = NULL;
//注意count要相应减少
H.count--;
return SUCCESS;
}
}

关于此处野指针的问题,具体可以参考 迷途指针

Test文件

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
c复制代码#include "ChashTable.h"

int hash(KeyType key, int size) {
return (3*key)%size;
}

void collision(int &hashValue, int hashSize) {//线性探测法
hashValue = (hashValue +1)% hashSize;
}

void TraverseCHashTable(CHashTable H){
Node* np;
int i;
for(i = 0; i<H.size; i++) {
printf("\n%d :", i);
for(np = H.rcd[i]; np!=NULL; np = np->next)
printf(" %d ", np->r.key);
}
}

void main(){
/********链地址哈希表*********/
CHashTable H;
int (*h)(KeyType, int);
h = hash;
InitHash(H, 11, h);

RcdType data[] = {22, 41, 53, 46, 30, 13, 12, 67};
int i;
for(i = 0; i<8; i++)
InsertHash(H, data[i]);

printf("原始链表\n");
TraverseCHashTable(H);
printf("\n");
RcdType e;
KeyType k[] = { 41,22,40 };
for (int i = 0; i < 3; i++) {
if (deleteHash(H, k[i], e) == SUCCESS) {
TraverseCHashTable(H);
printf("\n删除%d成功\n",k[i]);
}
else
{
TraverseCHashTable(H);
printf("\n删除%d失败\n",k[i]);
}

}

system("pause");
}

删除运行效果

构建哈希表题目

其实他本身帮我们初始化好了,H.size也给出来了

\

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
c复制代码#include "allinclude.h"  //DO NOT edit this line

//在哈希表中查找是否有key这个元素
HNode* SearchHash(ChainHashTab H,HKeyType key){
//找到在数组中的位置
int p = Hash(H,key);
HNode* np;
//在该位置上的同义词链表上搜索
for (np = H.rcd[p]; np != NULL; np = np->next) {
//找到则返回该指针
if (np->data == key) {
return np;
}
}
//找不到返回NULL
return NULL;
}

//向哈希表中插入key这个元素
Status InsertHash(ChainHashTab &H,HKeyType key){
int p;
HNode *np = SearchHash(H,key);
// 查找不成功时插入到表头
if(np == NULL) {
p = Hash(H,key);
np = (HNode*)malloc(sizeof(HNode));
//若分配失败,返回NULLKEY
if(np==NULL){
return NULLKEY;
}
np->data = key;
//先指向表头
np->next = H.rcd[p];
//再成为表头
H.rcd[p] = np;
H.count++;
return OK;
}
//若是重复插入,返回UNSUCCESS
else{
return UNSUCCESS;
}
}

int BuildHashTab(ChainHashTab &H, int n, HKeyType es[])
{ // Add your code here

for(int i=0;i<n;i++){
Status IfSucceed = InsertHash(H,es[i]);
//重复插入,并不意味着整个哈希表的构建失败了
//只有是插入的时候分配失败了,才意味着.....
if(NULLKEY == IfSucceed){
return ERROR;
}
}
return OK;
}

写在最后

  • 其实关于哈希表的实现还有很多种方式,而且不同语言的底层也不一样,等到以后深入Java集合源码的时候,我们还可以再来聊一聊!

作者:『Melo_』

出处:juejin.cn/user/299988…

本文版权归作者和掘金共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%