文章目录
哈希表
什么是哈希表
哈希表是构造出来的一种可以快速查找的存储结构。
哈希存储的基本思想是以关键字为自变量,通过一定的函数关系(称为散列函数或者哈希函数),计算出对应的函数值(简称算出他的哈希值),以这个值作为数据元素的地址,将该数据元素存到相应的地址单元中去。
查找时,再根据关键字采用计算哈希值的方式计算出相应的哈希地址,再到相应的存储单元去取需要的元素即可。
哈希表的优势
顺序结构和平衡树中,元素关键码与它的存储位置之间没有对应映射关系,在查找时需要多次比较,而哈希表可以通过哈希函数建立元素关键码与存储位置之间的对应关系,查找时经过哈希函数的计算,可以算出元素对应的存储位置,直接到存储位置取出要查找的元素,不需要像顺序结构或者平衡树那样多次比较。
哈希表的一般构造
**哈希表通常是什么样的呢?我们举一个非常简单的例子,我们以某个数字对5取余数的结果对数字进行分类,进而绘制一个哈希表。既然是对5取余运算,我们知道,其结果只能是有0,1,2,3,4五个数,因此分类有五种,索引也就是五种:**
假设我要存储一个 'A',那我要怎么做呢,首先我们A的Asall码是60,则对他取余(类比哈希值计算),则为零,所以我们就把A存入我们数组0这个位置就行了,是不是非常简单
重新举个例子,如果我是存入一个 'B',经过一系列构造的话,最终存入的是我们数组1的位置
哈希表冲突
对于两个元素,e1!=e2,但Hash(e1)=Hash(e2) ,就会产生哈希冲突,简单点说,就是两个不同的元素经过哈希函数的计算,计算除了相同的存储地址,这样的情况成为哈希冲突
举个简单的例子,拿我们上面的存入的'A'来说,如果此时再存入一个字符'F',Asall码值为70,则进过哈希运算的话(按上面的列子),最终也是存入数组0,但是我们数组0也已经有值了,所以会造成冲突
为何要避免哈希冲突
哈希表需要尽量将元素均匀的放入到每个存储位置中去,但是如果两个元素的关键码值相等,那么就会放到同一个元素中,如果这种情况很多,就会出现一个存储位置出现很多元素的情况。这样不利于查找。
如何避免哈希冲突
理论上如果哈希桶的数量多余要存储的位置,那么哈希冲突是可以避免的,但是实际中,我们认为要存储的元素是很多的,无穷的,哈希桶的数量是有限的,创建一个哈希桶也是需要耗费资源的,因此,实际中哈希冲突是不可避免的,因此,可以设计一些方法尽可能减少哈希冲突。
开放地址法
概念:
当发生哈希冲突时,如果哈希表未被填满,说明哈希表中还有空位置,那么就把元素放到下一个空位置去。
简单来说就是对他进行二次哈希
寻找空位置的方法可以用线性探测法(最简单的):
从冲突位置开始,依次向后查找,直到找到下一个空位置。
线性探测法
就是相当于在我们这个数后面一直累加一个数,直到哈希的值有位置给你进行存放
重新举个例子:
举个简单的例子,拿我们上面的存入的'A'来说,如果此时再存入一个字符'F',Asall码值为70,则进过哈希运算的话(按上面的列子),最终也是存入数组0,但是我们数组0也已经有值了,所以会造成冲突
那这样的话,我们在F后面加一个1,然后再对他进行一个哈希计算,算出来如果数组【1】有位置的话,则存放,没有的话再重新对它进行一个累加
二次探测法
第一次的时候加一个 1的平方
第二次的时候减一个 1的平方
第三次的时候加一个 2的平方
第四次的时候减一个 2的平方.........
直到有位置进行存放
随机探测法
在插入的值后面加入一个随机值,直到有位置进行存放
链地址法
简单来说,就是直接让数组指向一个空间,这个空间存储着冲突的节点
例如:
代码详解
c
#include <stdio.h>
#include <stdlib.h>
#define NUM 5
typedef struct HashList {
int num;
char* data;
} HashList;
HashList* initList() {
HashList* list = (HashList*)malloc(sizeof(HashList));
list -> num = 0;
list -> data = (char*)malloc(sizeof(char) * NUM);
for (int i = 0; i < NUM; i++) {
list -> data[i] = 0;
}
return list;
}
int hash(int data) {
return data % NUM;
}
void put(HashList* list, char data) {
int index = hash(data);
if (list -> data[index] != 0) {
int count = 1;
while (list -> data[index] != 0) {
index = hash(hash(data) + count);
count++;
}
}
list -> data[index] = data;
list -> num ++;
}
int main()
{
HashList* list = initList();
put(list, 'A');
put(list, 'F');
printf("%c\n", list -> data[0]);
printf("%c\n", list -> data[1]);
return 0;
}
☁️ 以上就是所有内容,对大家有用的话点个关注!感谢大家!
往期回顾
1.【第一章】《线性表与顺序表》
2.【第一章】《单链表》
3.【第一章】《单链表的介绍》
4.【第一章】《单链表的基本操作》
5.【第一章】《单链表循环》
6.【第一章】《双链表》
7.【第一章】《双链表循环》
8.【第二章】《栈》
9.【第二章】《队》
10.【第二章】《字符串暴力匹配》
11.【第二章】《字符串kmp匹配》
12.【第三章】《树的基础概念》
13.【第三章】《二叉树的存储结构》
14.【第三章】《二叉树链式结构及实现1》
15.【第三章】《二叉树链式结构及实现2》
16.【第三章】《二叉树链式结构及实现3》
17.【第三章】《二叉树链式结构及实现4》
18.【第三章】《二叉树链式结构及实现5》
19.【第三章】《中序线索二叉树理论部分》
20.【第三章】《中序线索二叉树代码初始化及创树》
21.【第三章】《中序线索二叉树线索化及总代码》
22【第三章】《先序线索二叉树理论及线索化》
23【第三章】《先序线索二叉树查找及总代码》
24【第三章】《后续线索二叉树线索化理论》
25【第三章】《后续线索二叉树总代码部分》
26【第三章】《二叉排序树基础了解》
27【第三章】《二叉排序树代码部分》
28【第三章】《二叉排序树代码部分》
29【第三章】《平衡二叉树基础概念》
30【第三章】《平衡二叉树的平衡因子》
31【第三章】《平衡二叉树的旋转基础详解》
32【第三章】《平衡二叉树的旋转类型图文详解》
33【第三章】《平衡二叉树的旋转类型总结及总代码》
34【第三章】《哈夫曼树简单了解》
35【第三章】《哈夫曼树的构造方法》
36【第三章】《哈夫曼编码构造及代码》
37【第四章】《图的定义》
38【第四章】《图的基本概念和术语》
39【第四章】《图的存储结构》
40【第四章】《图的遍历之深度优先遍历》
41【第四章】《广度优先遍历BFS》
42【第四章】《图的遍历总代码》
43【第四章】《最小生成树概念》
44【第四章】《最小生成树的应用举例》
45【第四章】《prim算法(普里姆算法)详解》
46【第四章】《prim算法(普里姆算法)详解2》
47【第四章】《prim算法(普里姆算法)详解3》
48【第四章】《prim算法(普里姆算法)讲解汇总》
49【第四章】《prim算法(普里姆算法)代码讲解》
50【第四章】《prim算法(普里姆算法)总代码》
51【第四章】《克鲁斯卡尔算法思路介绍》
52【第四章】《克鲁斯卡尔算法步骤思路1》
53【第四章】《克鲁斯卡尔算法步骤思路2》
54【第四章】《克鲁斯卡尔算法应用场景-公交站问题》
55【第四章】《克鲁斯卡尔算法判断回路问题》
56【第四章】《克鲁斯卡尔算法步骤回顾》
57【第四章】《克鲁斯卡尔算法代码初始化详解》
58【第四章】《克鲁斯卡尔算法总代码详解》
59【第四章】《了解最短路径》
60【第四章】《迪杰斯特拉算法了解》
61【第四章】《Dijkstra 迪杰斯特拉算法图解》
62【第四章】《Dijkstra 迪杰斯特拉算法总代码》
63【第四章】《弗洛伊德(floyd)算法简介》
64【第四章】《弗洛伊德算法详解》
65【第四章】《弗洛伊德代码详解》
66【第四章】《拓扑排序之AOV网》
67【第四章】《拓扑排序介绍及其方法》
68【第四章】《拓扑排序代码详解》
69【第四章】《什么是关键路径》
70【第四章】《什么是关键路径二》
71【第四章】《关键活动与最早路径实现思想》
72【第四章】《关键活动与最早路径实现思想写法二》
73【第四章】《关键路径总代码讲解写法一》
74【第四章】《关键路径总代码讲解写法二》
75【第五章】《顺序查找》
76【第五章】《顺序查找-带哨兵》
77【第五章】《二分查找》
78【第五章】《B树了解以及定义》
79【第五章】《B树的插入例子1》
80【第五章】《B树的插入例子2》
81【第五章】《B树的删除》
82【第五章】《B树的删除2》
83【第五章】《B树的代码部分》
84【第五章】《B树的总体代码》