散列桶的本质是哈希表,哈希表的本质是 K-V,K-V 不就是 map,那么这样一层一层学习下来,就能理解的更为透彻,学习编程一定要有追根刨底的好奇心,这样你的进步会非常快。
1、桶
就是可以存放数据的结构;在这里我认为桶就是结构体!
在哈希表的改进之上,哈希表当时自己的做法是:表中存放的是指针,而不能存放数据;
现在用桶存储:哈希表中的每个元素是结构体,有数据域和链域!(根据自己的情况不同而进行规定存放数据的多少和相应的有几个链域);其后哈希结点也可以这样定义,结点存放数据多少自己都可以定义;表中的结构体和结点的结构体没有什么联系!
在这我为了简单起见,让表中元素的结构体类型和链表结点类型一致。
模型图:2、实际问题
现在就是有一个存放整数的 Hash 表,Hash 表的存储单位叫做桶,每个桶存放 3 个整数,当一个桶中的元素超过 3 个时,要将新的元素存放在溢出桶中,每个溢出桶最多也只能放 3 个元素,多个溢出桶用链表串起来,此 Hash 表的基桶数目为 P,Hash 表的 hash 函数为对 P 取模,初始化桶时,要求数据默认为 -1,指针为 NULL,要求代码实现上述内容。
(1)、问题分析
Hash 表的存储单位叫做桶--->就是说 Hash 表元素的数据类型是结构体。
结构体中有 3 个数据域和 1 个指针域,溢出桶的结构体类型和表元素类型一致;哈希函数用的是除留余数法。
这个问题的模型和上面的那个图正好一一对应。
(2)、代码实现
在这我只实现了初始化,插入,输出的函数。
我把函数声明和定义都写在了 .h 文件中,只是为了方便;声明和定义本质上是应该分开写的。
以下代码都是纯 C 写的,与 C++ 没有牵扯。
1#ifndef _BUCKET_HASH_H_
2#define _BUCKET_HASH_H_
3
4#include
5
6typedef unsigned char boolean;
7
8#define TRUE 1
9#define FALSE 0
10
11#define P 7
12#define BUCKET_NODE_SIZE 3
13#define NULL_DATA -1
14
15//表和结点都是这个结构体类型
16typedef struct BUCKET_NODE{
17 ElemType data[BUCKET_NODE_SIZE];
18 struct BUCKET_NODE *next;
19}BUCKET_NODE;
20
21typedef BUCKET_NODE HashTable[P];
22
23int Hash(ElemType key){
24 return key % P;
25}
26//初始化哈希表,只不过是桶的形式。
27void initHashTable(HashTable ht);
28boolean insertNewElement(HashTable ht, ElemType value);
29void showHashTable(HashTable ht);
30
31void showHashTable(HashTable ht){ //显示表中元素
32 int i;
33 int j;
34 BUCKET_NODE *p;
35
36 for(i = 0; i 37 printf("%d : ", i);
38 if(ht[i].data[0] != NULL_DATA){
39 for(j = 0; j 40 if(ht[i].data[j] != NULL_DATA){
41 printf("%d ", ht[i].data[j]);
42 }
43 }
44 printf("-->");
45 p = ht[i].next;
46 while(p){
47 for(j = 0; j 48 if(p->data[j] != NULL_DATA){
49 printf("%d ", p->data[j]);
50 }
51 }
52 printf("-->");
53 p = p->next;
54 }
55 }
56 printf("NULL\n");
57 }
58}
59boolean insertNewElement(HashTable ht, ElemType value){
60 int index = Hash(value);
61 int i;
62 BUCKET_NODE *p;
63 BUCKET_NODE *q;
64 BUCKET_NODE *s;
65
66 for(i = 0; i //在这里先判断表中是否可以存放元素
67 if(ht[index].data[i] == NULL_DATA){
68 ht[index].data[i] = value;
69 return TRUE;
70 }
71 }
72 p = &ht[index]; //取表中某元素的地址
73 q = p->next; //指向了第一个结点,目的:保存前驱结点,在新生成结点时方便连接。
74 while(q){
75 for(i = 0; i //对链表中的元素进行一个一个遍历,看是否可以存储。
76 if(q->data[i] == NULL_DATA){
77 q->data[i] = value;
78 return TRUE;
79 }
80 }
81 p = q; //p永远保存的是前驱结点
82 q = q->next;
83 }
84
85 s = (BUCKET_NODE *)malloc(sizeof(BUCKET_NODE)); //此时,得生成新节点,用来存放数据
86 if(s == NULL){
87 return FALSE;
88 }
89 for(i = 0; i //对新生成的结点进行初始化工作
90 s->data[i] = NULL_DATA; //这里可以写个函数提取出来
91 }
92 s->next = NULL;
93 s->data[0] = value; //新生成的结点,对其赋值
94
95 p->next = s; //链域的连接。
96
97 return TRUE;
98
99}
100void initHashTable(HashTable ht){
101 int i;
102 int j;
103
104 for(i = 0; i 105 for(j = 0; j 106 ht[i].data[j] = NULL_DATA;
107 }
108 ht[i].next = NULL;
109 }
110}
111
112#endif
(3)、测试代码
1#include
2
3typedef int ElemType;
4
5#include"bucketHash.h"
6
7void main(void){
8 HashTable ht;
9 int ar[] = {1, 8, 15, 22, 29, 36, 43, 3, 5 ,99, 55, };
10 int n = sizeof(ar) / sizeof(int);
11 int i;
12
13 initHashTable(ht);
14 for(i = 0; i 15 insertNewElement(ht, ar[i]);
16 }
17
18 showHashTable(ht);
19}
(4)、测试结果
3、分析总结
(1)、我自己认为插入函数(尾插)是重点。
思路:我们在插入数据时,首先考虑的是哈希表中是否可以存放我们的数据;其次不是立马开辟空间,而是判断其后的链域空间是否可以存放;最后,就只能自己开辟空间,把数据存放到第一个位置。
(2)、这个问题还远远没有结束,还有好几个方法没有实现。
i>对新生成的结点进行前插;
ii>删除某一个数据,这个情况就比较多了,注意:删除数据,其它的应该前移,不然造成空间的浪费,对于结点中没有数据的应该释放这个空间;
iii>查找某一个结点的位置,通过返回值和参数,一个确定地址,一个确定在数组的下标;
iv>还有销毁函数;
.....................
(3)、以空间换时间的查找算法,哈希函数选取恰当,在较好的处理冲突的前提下,哈希查找的时间复杂度可以认为是O(1)。
推荐阅读:
从零开始学习数据结构-->入门篇
从零开始学习数据结构-->链表
从零开始学习数据结构-->线性表
从零开始学习数据结构-->栈
从零开始学习数据结构-->队列
从零开始学习数据结构-->矩阵+串
从零开始学习数据结构-->哈希表
认真的人 自带光芒