哈希表与布隆过滤器
一:哈希表:哈希表,是一种叫做哈希算法自然而然衍生出来的一种时间复杂度通常为o(!)的查找算法,哈希表的底层原理为数组下标以及对应值的映射,而数组通过下标index直接访问数组中所对应的值花费的时间复杂度是相对较少的,但是数组暴露的问题是不能动态分配所要的存储空间,导致有极大一部分资源会被浪费掉,哈希表同样也是通过下标来反映值,从高维到低微的映射,但是哈希表在查找元素时时间复杂度为o1,极大的减少时间复杂度的问题
二:哈希表和哈希算法:
哈希表存储的方式为取余存储
例如:val%size进行存储
比如我现在有一个值为6,此时哈希表的size长度为9,6取模于9为6所以存储在下标为6的地方
相同的原理我有一个值为7的要进行哈希表的村存储则会存储到哈希表下标为7的地方,总之就是val%size为多少就存储在哈希表下标为多少的地方
6 | 7 |
1 2 3 4 5 6 7 8 9
但是这样就会存在一个问题,我val=6和val=15时候所取余结果是相同的,这种情况就要将数值存储在相同的下标,但是又不满足数组唯一下标访问唯一值的原理,如下图
6/15 |
这种显然是不行的,所以就产生了新的名词叫做哈希冲突,我们把这种情况称作哈希冲突
既然有哈希冲突,那么肯定就有解决哈希冲突的办法,下面是解决哈希冲突的四种方法,我会将最常用并且最实用的方法换一种颜色来进行标记
1:哈希冲突解决办法一:
开放定址法:如果所存储的地方存在值
则从所在值的下标进行寻找找到一个没有存值的地方来将值存放进去
最初选用的地址存放的方法为遍历,但是由于遍历可利用性以及花费时间过多,后来改为平方相加存值,通过平方数的相加来大范围寻找是否有合适的空地址可以进行存放
2:哈希冲突的解决办法二:
再哈希法:没有合适的哈希地址存放,那就再造一个哈希地址进行存放
但是个人感觉这种方法不太实用,因为哈希函数的构造本身是有一定难度的,如果多次构造会产生代码结构错误或者突兀,总之就是这是方法偏向理论感觉会更多一点
3:哈希冲突的解决办法三:
建立公共溢出区:将所有没有合适地址存放的数值全部存入到公共溢出区种中去,最后再进行合理的分配,建立公共溢出区中的溢出区可以是多种数据结构比如说是栈,红黑二叉树,队列等多种数据结构
4:哈希冲突的解决办法四:
拉链法:拉链法是一种相比其他方法更加可以偏向实际操作和具体实现的一种方法,它的逻辑方法为将有冲突的地址存放到一起,将所有有冲突的数值通过指针指向的方法来在当前地址构造成为一条链表的方法,看图可能会比较清晰一点
xxxxxxx |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
比如我在xxxxxx这个位置有1-9的数值的冲突,那么我就在这块地址构造一个链表用来存放相同的地址不同的冲突值,查找时候只需要锁定原哈希表地址下对应的链表结构来查找数值即可
三:哈希表衍生出的一种新的容器
布隆过滤器
它的底层原理是通过哈希函数的哈希冲突来进行搜索有无重复信息来进行过滤
比如一个巨大的随机数
比如python爬虫爬到的百亿条的网页地址都可以进行大概率的过滤
布隆过滤器是一种概率性过滤器,如果出现有重复元素可能是大概率出现过此元素,从而进行过滤,但是如果要求的数据必须准确和精确使用布隆过滤器可能会有点麻烦,但是如果布隆过滤器没有出现过则可以断定此时元素或者数据一定没有出现
如何增大布隆过滤的过滤概率,通过增加哈希函数的数量和增大布隆过滤器底层的标注空间的都可以进行增大布隆过滤器的效率
四:哈希表的代码演示
#include<stdio.h>
#include<stdlib.h>
typedef struct Node
{
char* s;//字符串数据类型,想要是其他数据结构可进行自行更换
struct Node* next;
}Node;//哈希表中的链表的存储结构
typedef struct hash_table
{
Node* vector;
int count, size;//conunt代表一个hashtable中存放的元素数量,size代表哈希表中的数组长度
}hash_table;
Node* getnewnode(char *data)
{
Node*p=(Node*)malloc(sizeof(Node));
p->next = NULL;
p->s = data;
return p;
}//Node节点初始化
hash_table* getnewhashtable(int n)
{
hash_table* p = (hash_table*)malloc(sizeof(hash_table) * n);
p->vector = (Node*)malloc(sizeof(Node) * n);
p->count = 0;
p->size = n;
return p;
}//hash表中的表类初始化和节点初始化
int hash_func(char* s)
{
//字符串哈希算法
int need = 131, hash = 0;
for (int i = 0; i < s[i]; i++){
hash *= need + s[i];
}
return hash & 0xfffff;
}//哈希算法操作
bool findhash(hash_table* h, char* s)
{
if (h == NULL) return false;
int hcode = hash_func(s);
int index = hcode % h->size;
Node* p = h->vector[index].next;
while (p)
{
if (p->s == h->vector[index].s)
{
return true;
}
p = p->next;
}
return false;
}//查找操作
void swap1(int num1, int num2)
{
hash_table* h1, * h2;
int temp1 = h1->size;
h1->size = h2->size;
h2->size = temp1;
return;
}//交换哈希表中的所改变的长度
void swap2(int num1, int num2)
{
hash_table* h1, * h2;
int temp1 = h1->count;
h1->count = h2->count;
h2->count = temp1;
return;
}//交换哈希表中的元素
void swap3(Node* p1, Node* p2)
{
hash_table* h1, * h2;
//交换节点next
Node* temp = h1->vector->next;
h1->vector->next = h2->vector->next;
h2->vector->next = temp;
//交换节点的字符串数值
char *temps = h1->vector->s;
h1->vector->s = h2->vector->s;
h2->vector->s=temps;
return;
}
void swaphash(hash_table* h1,hash_table *h2)
{
swap1(h1->count, h2->count);
swap2(h1->size, h2->size);
swap3(h1->vector, h2->vector);
}//哈希表中所有内置数据的操作
void expand(hash_table* h)
{
//哈希表扩容操作
hash_table* new_hash_table = getnewhashtable(h->size * 2);
for (int i = 0; i < h->size; i++)
{
Node* p = h->vector[i].next;
while (p)
{
insert(new_hash_table, p->s);
p = p->next;
}
}
swaphash(h, new_hash_table);
return;
}
bool insert(hash_table* h, char* s)
{
if (h->count <= h->size) expand(h);
if (h == NULL) return false;
int hcode = hash_func(s);
int index = hcode % h->size;
//冲突处理
//拉链法
Node* p = getnewnode(s);//将链表封装为哈希表的节点
p->next = h->vector[index].next;
h->vector[index].next = p;
h->count += 1;
return true;
}
void deletehashtable(hash_table* h)
{
//先回收hash表中的节点
//再回收哈希表
if (h == NULL)return;
for (int i = 0; i < h->size; i++)
{
//销毁数据存储区中的每一个链表结构
Node* p = h->vector[i].next, * q;
while (p)
{
q = p->next;
deleteNode(p);
p = q;
}
}
free(h->vector);//回收hash表中的数据空间
free(h);//回收hash表中的本身的空间
return;
}
void deleteNode(Node *p)
{
if (p == NULL) return;
if (p->s) free(p->s);
free(p);
return;
}//回收hash表中链表的节点
int main()
{
return 0;
}
上面代码为hash表的操作实现,具体有注释进行各个操作
如何应用哈希算法来进行解题和设计见下一文章,hash的具体实现和用法
如有侵权,请联系我