最近在学习通信协议,链路层传输时,对于报文的识别处理,建立哈希识别规则;之前简单学习过哈希,还没深入过,闲来无事把学习的内容通俗的表达出来,也和大家分享一下,让新入门的童鞋很快理解。
1、哈希表
我们都学过函数,三要素:自变量x,因变量y,对应关系f。
在函数里x、y通过映射关系f,一一对应起来。
那什么是哈希呢?
就是记录的储存位置和他的关键字之间建立一个确定的对应关系f,这里我们就可以吧这种对应关系f称之为哈希(Hash)函数。
那哈希表呢?
采用哈希函数将记录储存在一块连续的存储空间中,这块连续的储存空间就称之为哈希表(Hash table)
看到这你应该是明白定义了吧。
当然哈希表不仅可以用来储存,更是可以用来查找,效率是很高的,因为一一对应嘛,查找算法时间复杂度为O(1)!哈希表的建立的关键是哈希函数的构建,也就是如何形成key和value的对应。
理想情况当然是一一对应的关系最好,但是现实情况下,难免碰到f(key1)=f(key2),我们称之为冲突,key1同key2我们可以称为“同义词”所以构建哈希函数,处理冲突也就是很关键的事情了。
这里简单介绍几个建立哈希关系的方法,但不做深入探究,有兴趣的童鞋可以自行查找学习。
1.1直接定址法
f(key)=a*key+b
即取关键字的某个线性函数作为散列(哈希)地址;但我们需要知道关键字的分布,现实情况很少用。
1.2数字分析法
对于刚刚入学的童鞋都会有个学号,而前几位肯定相同,后面几位每个人都不同,这时(不用)分析便可知道,抽取后面几位,对这些数字进行操作,使之不相同,总而言之就是提供一个哈希函数,能够让关键字分配到哈希表中去。
1.3平方取中法
也就是说将数字平方,取中间几位作为关键字。
1.4除数取余法
假如你有n个数字
f(key)=key%p(p=<n)
也可平方后操作,当然这里关键在于找到合适的p
key | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
value | 12 | 25 | 38 | 15 | 16 | 29 | 78 | 67 | 56 | 21 | 22 | 47 |
这里p=12,正好一一对应,嘿嘿嘿。
1.5随机数法
f(key)=random(key)
就是用了random函数
额还有很多不写了。
然而事实情况是:构建这样的函数很难,所以要去解决这种冲突
也就是解决key1不等于key2,但f(key1)=f(key2)。
方法不少,列举一下吧,就不细说了,有兴趣的童鞋自行搜索学习。
1、开发定址法 2、再散列函数法 3、链地址法
2 哈希桶
实际上哈希桶是解决哈希表冲突的一种方法。常见的解决冲突的两种方法:1、分离链接法 2、开放定址法。其中使用分离链接法,得到的对应关系即为哈希桶。
哈希表中同一个位置可能存有多个元素,为应对哈希冲突问题,将哈希表中的每个位置表示一个哈希桶。
2.1哈希桶示意
每个key被hash(…)到相同的孔,那么如何解决冲突呢 就是在那个孔放一个链表
这个孔下面的链表就像桶一样盛着key对应的元素。
下面通过除数取余+分离链接画图简单说明下:
这里将冲突的元素插入到各桶头部,即链表头。
哈希桶就是盛放不同key链表的容器,在这里我们可以把每个key的位置看作是一个桶,桶里放了一个链表,故该方法也叫分离链接法。
另外,前面有说过,哈希表的查找算法时间复杂度是O(1)
,学过算法的同学,肯定知道哈希是以空间换取时间的,如果这里哈希通的key选择不当,复杂度将迅速升为O(n)
(当全部元素对应一个key值,退化为链表)
2.1哈希桶简单实现
代码很不难,学习的小伙伴耐心分析看看;我定义的节点里包括key和value
,实际上只需要一个值就可以生成对应哈希值;另外由于我定义时,用的数组,所以生成key是连续的,只是简单示例下。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUCKETCOUNT 10
/*链表节点*/
struct Node{
char* key;
char* value;
struct Node *next;
};
typedef struct Node node;
/*顺序哈希桶*/
struct hashTable{
node bucket[BUCKETCOUNT];/*先默认定义10个桶*/
};
typedef struct hashTable table;
void initHashTable();
int hash(const char* key);
struct Node* lookup(const char* key);
int insertnode(table *t, char *key, char *value);
const char* get(const char* key);
void display();
/*初始化哈希桶*/
void initHashTable(table *t){
int i;
if(!t)
return;
for(i = 0; i < BUCKETCOUNT; ++i){
t->bucket[i].key = NULL;
t->bucket[i].next = NULL;
t->bucket[i].value = NULL;
}
}
/*哈希函数,根据生成哈希值*/
int hash(const char* key){
int hash=0;
for (; *key; ++key)
{
hash=hash*33+*key;
}
return hash%BUCKETCOUNT;
}
/*插入数据到hash表中*/
int insertnode(table *t,char *key,char *value){
int index, vlen1, vlen2;
node *e, *ep;
if(t == NULL || key == NULL || value == NULL) return -1;
index = hash(key);
//如果现在桶为空,直接写入第一个桶节点
if(!t->bucket[index].key){
t->bucket[index].key = strdup(key);
t->bucket[index].value = strdup(value);
}
else{
e = ep = &(t->bucket[index]);
/*先从已经存在的找*/
while(e){
/*如果key值重复,替换相应的值*/
if(strcmp(e->key, key) == 0){
vlen1 = strlen(value);
vlen2 = strlen(e->value);
if(vlen1 > vlen2){
free(e->value);
e->value = (char *)malloc(sizeof(vlen1 + 1));
}
memcpy(e->value, value, vlen1 + 1);
return index;/*插入完成*/
}
ep = e;
e = e->next;
}
/*没有在当前桶中找到,创建新的节点加入,并加入桶链表*/
e = (node *)malloc(sizeof(node));
if(NULL != e){
e->key = strdup(key);
e->value = strdup(value);
e->next = NULL;
ep->next = e;
}
}
return index;
}
/*打印hash表*/
void display(table *t){
int i;
node *e;
if(!t) return;
for(i = 0; i < BUCKETCOUNT; i++){
printf("bucket[%d]:", i);
e = &(t->bucket[i]);
while(e){
printf("%s = %s\t\t",e->key, e->value);
e = e->next;
}
printf("\n");
}
}
int main(int argc, char* argv[])
{
table *ht;
ht = (table *)malloc(sizeof(table));
initHashTable(ht);
char* key[]={"a","b","c","d","e","f","g","h","i","j",
"k","l","m","n","o","p","q","r","s","t",
"u","v","w","x","y","z"};
char* value[]={"apple","banana","china","doge","egg","fox",
"goat","hello","ice","jeep","key","love",
"mirror","net","orange","panda","quarter","rabbit",
"shark","tea","unix","volley","wolf","x-ray","yard","zoo"};
for (int i = 0; i < 26; ++i)
{
insertnode(ht, key[i], value[i]);
}
display(ht);
return 0;
}
执行下看看效果:
PS:
函数**strdup()**
头文件: string.h
功 能: 将串拷贝到新建的位置处,strdup()在内部调用了malloc()为变量分配内存,不需要使用返回的字符串时,需要用free()释放相应的内存空间,否则会造成内存泄漏。
返回值: 返回一个指针,指向为复制字符串分配的空间;如果分配空间失败,则返回NULL值。