1开放定址法感觉不太好用,刷题也不常见,所以我就没学...就看一下拉链法好了
2哈希表:拉链法笔记
3
41. 前提条件
5哈希表本质:
6key
7↓
8hash函数
9↓
10数组下标
11
12例如:hash(key) = key % 10
13
14插入:21
15
16得到:21 % 10 = 1
17
18所以放到:table[1]
19
202. 核心概念
21
22如果没有冲突:
23table[1] = 21
24
25如果冲突:
26
2721 % 10 = 1
2831 % 10 = 1
2941 % 10 = 1
30
31都落到下标 1。
32
33拉链法不是往后找空位,而是:
34
35table[1] -> 21 -> 31 -> 41
36
37也就是:数组 + 链表
38
393. 结构理解
40
41数组每个位置叫一个桶:
42
43table[0]
44table[1]
45table[2]
46...
47
48每个桶里放一条链表。
49
50例如:
51
52下标: 0 1 2 3
53 NULL 21->31->41 NULL 13->23
54
55所以查找 31:
56
5731 % 10 = 1
58
59先找到:table[1]
60
61然后在这条链表里找:21 -> 31
62
634. 结构体模板
64
65这里我们存两样东西:
66
67key:数组值
68value:数组下标
69
70因为两数之和要返回原下标。
71
72#include <stdlib.h>
73#include <stdbool.h>
74
75#define HASH_SIZE 10007
76
77struct HashNode {
78 int key;
79 int value;
80 struct HashNode* next;
81};
82
83struct HashTable {
84 struct HashNode* table[HASH_SIZE];
85};
86
875. 哈希函数
88
89C 里面 % 遇到负数可能得到负结果,所以要处理一下。
90
91int hash(int key)
92{
93 int h = key % HASH_SIZE;
94
95 if(h < 0)
96 {
97 h += HASH_SIZE;
98 }
99
100 return h;
101}
102
103例如:
104
105key = -3
106
107先:
108
109-3 % 10007 = -3
110
111修正:
112
113-3 + 10007
114
115变成合法下标。
116
1176. 初始化哈希表
118void initHashTable(struct HashTable* h)
119{
120 for(int i = 0; i < HASH_SIZE; i++)
121 {
122 h->table[i] = NULL;
123 }
124}
125
126意思:
127
128每个桶一开始都是空链表
129
1307. 查找
131struct HashNode* find(struct HashTable* h, int key)
132{
133 int index = hash(key);
134
135 struct HashNode* cur = h->table[index];
136
137 while(cur != NULL)
138 {
139 if(cur->key == key)
140 {
141 return cur;
142 }
143
144 cur = cur->next;
145 }
146
147 return NULL;
148}
149
150理解:
151
1521. 先算桶下标
1532. 找到 table[index]
1543. 在链表里顺着 next 找
1558. 插入
156
157这里用头插法。
158
159void insert(struct HashTable* h, int key, int value)
160{
161 int index = hash(key);
162
163 struct HashNode* node =
164 malloc(sizeof(struct HashNode));
165
166 node->key = key;
167 node->value = value;
168
169 node->next = h->table[index];
170
171 h->table[index] = node;
172}
173
174例如桶里原来:
175
176table[1] -> 21 -> 31
177
178插入 41:
179
180node->next = table[1]
181table[1] = node
182
183结果:
184
185table[1] -> 41 -> 21 -> 31
186
187这就是链表头插法。
188
1899. 释放哈希表
190
191C 里 malloc 了节点,就要 free。
192
193void freeHashTable(struct HashTable* h)
194{
195 for(int i = 0; i < HASH_SIZE; i++)
196 {
197 struct HashNode* cur = h->table[i];
198
199 while(cur != NULL)
200 {
201 struct HashNode* next = cur->next;
202 free(cur);
203 cur = next;
204 }
205
206 h->table[i] = NULL;
207 }
208}
209
21010. 拉链法完整模板
211#include <stdlib.h>
212#include <stdbool.h>
213
214#define HASH_SIZE 10007
215
216struct HashNode {
217 int key;
218 int value;
219 struct HashNode* next;
220};
221
222struct HashTable {
223 struct HashNode* table[HASH_SIZE];
224};
225
226int hash(int key)
227{
228 int h = key % HASH_SIZE;
229
230 if(h < 0)
231 {
232 h += HASH_SIZE;
233 }
234
235 return h;
236}
237
238void initHashTable(struct HashTable* h)
239{
240 for(int i = 0; i < HASH_SIZE; i++)
241 {
242 h->table[i] = NULL;
243 }
244}
245
246struct HashNode* find(struct HashTable* h, int key)
247{
248 int index = hash(key);
249
250 struct HashNode* cur = h->table[index];
251
252 while(cur != NULL)
253 {
254 if(cur->key == key)
255 {
256 return cur;
257 }
258
259 cur = cur->next;
260 }
261
262 return NULL;
263}
264
265void insert(struct HashTable* h, int key, int value)
266{
267 int index = hash(key);
268
269 struct HashNode* node =
270 malloc(sizeof(struct HashNode));
271
272 node->key = key;
273 node->value = value;
274
275 node->next = h->table[index];
276
277 h->table[index] = node;
278}
279
280void freeHashTable(struct HashTable* h)
281{
282 for(int i = 0; i < HASH_SIZE; i++)
283 {
284 struct HashNode* cur = h->table[i];
285
286 while(cur != NULL)
287 {
288 struct HashNode* next = cur->next;
289 free(cur);
290 cur = next;
291 }
292
293 h->table[i] = NULL;
294 }
295}
296
29711. LeetCode 1 两数之和:哈希实现
298核心思想
299
300遍历每个数 nums[i]。
301
302当前数是:x = nums[i]
303
304想要另一个数:need = target - x
305
306先去哈希表查:need 是否出现过
307
308如果出现过,直接返回:
309
310need 的下标
311当前 i
312
313如果没出现过,就把当前数存进去:
314
315key = nums[i]
316value = i
317
31812. 为什么先查再插?
319
320例如:
321
322nums = [3,3]
323target = 6
324
325i = 0:
326
327x = 3
328need = 3
329
330表里没有 3。
331
332插入:
333
3343 -> 0
335
336i = 1:
337
338x = 3
339need = 3
340
341查到之前的:
342
3433 -> 0
344
345返回:
346
347[0,1]
348
349如果先插再查,可能会把自己和自己配对。
350
351所以:
352
353先查 need
354再插 x
35513. 两数之和 C 代码
356#include <stdlib.h>
357#include <stdbool.h>
358
359#define HASH_SIZE 10007
360
361struct HashNode {
362 int key;
363 int value;
364 struct HashNode* next;
365};
366
367struct HashTable {
368 struct HashNode* table[HASH_SIZE];
369};
370
371int hash(int key)
372{
373 int h = key % HASH_SIZE;
374
375 if(h < 0)
376 {
377 h += HASH_SIZE;
378 }
379
380 return h;
381}
382
383void initHashTable(struct HashTable* h)
384{
385 for(int i = 0; i < HASH_SIZE; i++)
386 {
387 h->table[i] = NULL;
388 }
389}
390
391struct HashNode* find(struct HashTable* h, int key)
392{
393 int index = hash(key);
394
395 struct HashNode* cur = h->table[index];
396
397 while(cur != NULL)
398 {
399 if(cur->key == key)
400 {
401 return cur;
402 }
403
404 cur = cur->next;
405 }
406
407 return NULL;
408}
409
410void insert(struct HashTable* h, int key, int value)
411{
412 int index = hash(key);
413
414 struct HashNode* node =
415 malloc(sizeof(struct HashNode));
416
417 node->key = key;
418 node->value = value;
419
420 node->next = h->table[index];
421
422 h->table[index] = node;
423}
424
425void freeHashTable(struct HashTable* h)
426{
427 for(int i = 0; i < HASH_SIZE; i++)
428 {
429 struct HashNode* cur = h->table[i];
430
431 while(cur != NULL)
432 {
433 struct HashNode* next = cur->next;
434 free(cur);
435 cur = next;
436 }
437 }
438}
439
440int* twoSum(int* nums, int numsSize, int target, int* returnSize)
441{
442 struct HashTable h;
443
444 initHashTable(&h);
445
446 int* ans = malloc(2 * sizeof(int));
447
448 *returnSize = 2;
449
450 for(int i = 0; i < numsSize; i++)
451 {
452 int x = nums[i];
453
454 int need = target - x;
455
456 struct HashNode* found = find(&h, need);
457
458 if(found != NULL)
459 {
460 ans[0] = found->value;
461 ans[1] = i;
462
463 freeHashTable(&h);
464
465 return ans;
466 }
467
468 insert(&h, x, i);
469 }
470
471 *returnSize = 0;
472
473 free(ans);
474 freeHashTable(&h);
475
476 return NULL;
477}
478
47914. 例子流程
480nums = [2,7,11,15]
481target = 9
482
483i = 0:
484
485x = 2
486need = 7
487
488查 7:
489
490没找到
491
492插入:
493
4942 -> 0
495
496i = 1:
497
498x = 7
499need = 2
500
501查 2:
502
503找到了,下标 0
504
505返回:
506
507[0,1]
50815. 易错点
5091. key 和 value 不一样
510
511在两数之和里:
512
513key = nums[i]
514value = i
515
516不能只存值,因为答案要返回下标。
517
5182. 负数 hash 要处理
519int h = key % HASH_SIZE;
520
521if(h < 0)
522{
523 h += HASH_SIZE;
524}
5253. 先查再插
526
527避免同一个元素用两次。
528
5294. 冲突用链表解决
530
531同一个桶里可能有多个节点:
532
533table[index] -> node1 -> node2 -> node3
534
535查找时要遍历这条链。
536
537笔记版总结
538拉链法哈希表
539
540--------------------------------
541
542结构:
543
544数组 + 链表
545
546数组每个位置叫桶 bucket
547
548每个桶里是一条链表
549
550--------------------------------
551
552哈希函数:
553
554index = key % HASH_SIZE
555
556--------------------------------
557
558冲突:
559
560多个 key 得到同一个 index
561
562--------------------------------
563
564解决:
565
566把它们挂到同一个桶的链表里
567
568--------------------------------
569
570插入:
571
5721. 计算 index
5732. 创建新节点
5743. 头插到 table[index]
575
576--------------------------------
577
578查找:
579
5801. 计算 index
5812. 遍历 table[index] 链表
5823. 找到 key 返回节点
5834. 找不到返回 NULL
584
585--------------------------------
586
587两数之和:
588
589遍历 nums[i]
590
591x = nums[i]
592
593need = target - x
594
595先查 need 是否出现过
596
597如果出现过:
598 返回 need 的下标和 i
599
600否则:
601 插入 x 和 i
602
603--------------------------------
604
605key = 数值
606value = 下标
607
608一句话:
609
610拉链法就是:数组快速定位桶,链表解决同桶冲突;两数之和就是边查 need,边存当前数。
611看一下py的字典实现,因为内置了字典,而c需要手写底层hash
612
613def twoSum(nums, target):
614 mp = {}
615
616 for i, x in enumerate(nums):
617 need = target - x
618
619 if need in mp:
620 return [mp[need], i]
621
622 mp[x] = i
开放定址法感觉不太好用,刷题也不常见,所以我就没学...就看一下拉链法好了
哈希表:拉链法笔记
- 前提条件
哈希表本质:
key
↓
hash函数
↓
数组下标
例如:hash(key) = key % 10
插入:21
得到:21 % 10 = 1
所以放到:table[1]
- 核心概念
如果没有冲突:
table[1] = 21
如果冲突:
21 % 10 = 1
31 % 10 = 1
41 % 10 = 1
都落到下标 1。
拉链法不是往后找空位,而是:
table[1] -> 21 -> 31 -> 41
也就是:数组 + 链表
- 结构理解
数组每个位置叫一个桶:
table[0]
table[1]
table[2]
...
每个桶里放一条链表。
例如:
下标: 0 1 2 3
NULL 21->31->41 NULL 13->23
所以查找 31:
31 % 10 = 1
先找到:table[1]
然后在这条链表里找:21 -> 31
- 结构体模板
这里我们存两样东西:
key:数组值
value:数组下标
因为两数之和要返回原下标。
#include <stdlib.h>
#include <stdbool.h>
#define HASH_SIZE 10007
struct HashNode {
int key;
int value;
struct HashNode* next;
};
struct HashTable {
struct HashNode* table[HASH_SIZE];
};
- 哈希函数
C 里面 % 遇到负数可能得到负结果,所以要处理一下。
int hash(int key)
{
int h = key % HASH_SIZE;
if(h < 0)
{
h += HASH_SIZE;
}
return h;
}
例如:
key = -3
先:
-3 % 10007 = -3
修正:
-3 + 10007
变成合法下标。
- 初始化哈希表
void initHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
h->table[i] = NULL;
}
}
意思:
每个桶一开始都是空链表
-
查找
struct HashNode* find(struct HashTable* h, int key)
{
int index = hash(key);
struct HashNode* cur = h->table[index];
while(cur != NULL)
{
if(cur->key == key)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
理解:
- 先算桶下标
- 找到 table[index]
- 在链表里顺着 next 找
- 插入
这里用头插法。
void insert(struct HashTable* h, int key, int value)
{
int index = hash(key);
struct HashNode* node =
malloc(sizeof(struct HashNode));
node->key = key;
node->value = value;
node->next = h->table[index];
h->table[index] = node;
}
例如桶里原来:
table[1] -> 21 -> 31
插入 41:
node->next = table[1]
table[1] = node
结果:
table[1] -> 41 -> 21 -> 31
这就是链表头插法。
- 释放哈希表
C 里 malloc 了节点,就要 free。
void freeHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
struct HashNode* cur = h->table[i];
while(cur != NULL)
{
struct HashNode* next = cur->next;
free(cur);
cur = next;
}
h->table[i] = NULL;
}
}
- 拉链法完整模板
#include <stdlib.h>
#include <stdbool.h>
#define HASH_SIZE 10007
struct HashNode {
int key;
int value;
struct HashNode* next;
};
struct HashTable {
struct HashNode* table[HASH_SIZE];
};
int hash(int key)
{
int h = key % HASH_SIZE;
if(h < 0)
{
h += HASH_SIZE;
}
return h;
}
void initHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
h->table[i] = NULL;
}
}
struct HashNode* find(struct HashTable* h, int key)
{
int index = hash(key);
struct HashNode* cur = h->table[index];
while(cur != NULL)
{
if(cur->key == key)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void insert(struct HashTable* h, int key, int value)
{
int index = hash(key);
struct HashNode* node =
malloc(sizeof(struct HashNode));
node->key = key;
node->value = value;
node->next = h->table[index];
h->table[index] = node;
}
void freeHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
struct HashNode* cur = h->table[i];
while(cur != NULL)
{
struct HashNode* next = cur->next;
free(cur);
cur = next;
}
h->table[i] = NULL;
}
}
- LeetCode 1 两数之和:哈希实现
核心思想
遍历每个数 nums[i]。
当前数是:x = nums[i]
想要另一个数:need = target - x
先去哈希表查:need 是否出现过
如果出现过,直接返回:
need 的下标
当前 i
如果没出现过,就把当前数存进去:
key = nums[i]
value = i
- 为什么先查再插?
例如:
nums = [3,3]
target = 6
i = 0:
x = 3
need = 3
表里没有 3。
插入:
3 -> 0
i = 1:
x = 3
need = 3
查到之前的:
3 -> 0
返回:
[0,1]
如果先插再查,可能会把自己和自己配对。
所以:
先查 need
再插 x
13. 两数之和 C 代码
#include <stdlib.h>
#include <stdbool.h>
#define HASH_SIZE 10007
struct HashNode {
int key;
int value;
struct HashNode* next;
};
struct HashTable {
struct HashNode* table[HASH_SIZE];
};
int hash(int key)
{
int h = key % HASH_SIZE;
if(h < 0)
{
h += HASH_SIZE;
}
return h;
}
void initHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
h->table[i] = NULL;
}
}
struct HashNode* find(struct HashTable* h, int key)
{
int index = hash(key);
struct HashNode* cur = h->table[index];
while(cur != NULL)
{
if(cur->key == key)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void insert(struct HashTable* h, int key, int value)
{
int index = hash(key);
struct HashNode* node =
malloc(sizeof(struct HashNode));
node->key = key;
node->value = value;
node->next = h->table[index];
h->table[index] = node;
}
void freeHashTable(struct HashTable* h)
{
for(int i = 0; i < HASH_SIZE; i++)
{
struct HashNode* cur = h->table[i];
while(cur != NULL)
{
struct HashNode* next = cur->next;
free(cur);
cur = next;
}
}
}
int* twoSum(int* nums, int numsSize, int target, int* returnSize)
{
struct HashTable h;
initHashTable(&h);
int* ans = malloc(2 * sizeof(int));
*returnSize = 2;
for(int i = 0; i < numsSize; i++)
{
int x = nums[i];
int need = target - x;
struct HashNode* found = find(&h, need);
if(found != NULL)
{
ans[0] = found->value;
ans[1] = i;
freeHashTable(&h);
return ans;
}
insert(&h, x, i);
}
*returnSize = 0;
free(ans);
freeHashTable(&h);
return NULL;
}
- 例子流程
nums = [2,7,11,15]
target = 9
i = 0:
x = 2
need = 7
查 7:
没找到
插入:
2 -> 0
i = 1:
x = 7
need = 2
查 2:
找到了,下标 0
返回:
[0,1]
15. 易错点
- key 和 value 不一样
在两数之和里:
key = nums[i]
value = i
不能只存值,因为答案要返回下标。
- 负数 hash 要处理
int h = key % HASH_SIZE;
if(h < 0)
{
h += HASH_SIZE;
}
3. 先查再插
避免同一个元素用两次。
- 冲突用链表解决
同一个桶里可能有多个节点:
table[index] -> node1 -> node2 -> node3
查找时要遍历这条链。
笔记版总结
拉链法哈希表
结构:
数组 + 链表
数组每个位置叫桶 bucket
每个桶里是一条链表
哈希函数:
index = key % HASH_SIZE
冲突:
多个 key 得到同一个 index
解决:
把它们挂到同一个桶的链表里
插入:
- 计算 index
- 创建新节点
- 头插到 table[index]
查找:
- 计算 index
- 遍历 table[index] 链表
- 找到 key 返回节点
- 找不到返回 NULL
两数之和:
遍历 nums[i]
x = nums[i]
need = target - x
先查 need 是否出现过
如果出现过:
返回 need 的下标和 i
否则:
插入 x 和 i
key = 数值
value = 下标
一句话:
拉链法就是:数组快速定位桶,链表解决同桶冲突;两数之和就是边查 need,边存当前数。
看一下py的字典实现,因为内置了字典,而c需要手写底层hash
def twoSum(nums, target):
mp = {}
for i, x in enumerate(nums):
need = target - x
if need in mp:
return [mp[need], i]
mp[x] = i