博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
hash表的C实现和hash桶的示例
阅读量:2392 次
发布时间:2019-05-10

本文共 3345 字,大约阅读时间需要 11 分钟。

 

此段转载  

hash是以空间换时间的结构,现在空间越来越大,而且对性能要求越来越高的年代,这绝对是值得的。

hash含义就是散列,就是把我们本来想​查找的一大群结构体数据分散开,更容易查找。一个好的hash函数应该做到对所有元素平均分散排列,尽量避免或者降低他们之间的冲突(Collision)。hash函数的选择必须慎重,如果不幸所有的元素之间都产生了冲突,那么hash表将退化为链表,其性能会大打折扣,时间复杂度迅速降为O(n),绝对不要存在任何侥幸心理,因为那是相当危险的。历史上就出现过利用Linux内核hash函数的漏洞,成功构造出大量使hash表发生碰撞的元素,导致系统被DoS,所以目前内核的大部分hash函数都有一个随机数作为参数进行掺杂,以使其最后的值不能或者是不易被预测。这又对 hash函数提出了第二点安全方面的要求:hash函数最好是单向的,并且要用随机数进行掺杂。提到单向,你也许会想到单向散列函数md4和md5,很不幸地告诉你,他们是不适合的,因为hash函数需要有相当好的性能。

散列函数: 将字符串等传入我们的散列函数,而散列函数的职责就是给我们返回一个value值,我们通过这个值引做hash表下标去访问我们想要查找的数据;例如这样的函数:

int Hash(char *key, int TableSize)

{
     unsigned int HashVal = 0;
     while(*key != '\0')
             HashVal += *key++;
     return HashVal % TableSize;
}
这就是一个简单的hash函数,就是把我们传入过来的key(由我们的数据中一个或者多个结构体成员的成员来作为key)来得到一个返回值,这个返回值就是我们的value值。

一个好的hash​函数就是把我们的说有数据尽可能均匀的分散在我们预设的TableSize大小的hash表中。哈希表的几种方法:

1、直接定址法:取关键字key的某个线性函数为散列地址,如Hash(key) = key 或 Hash(key) = A*key+B;A,B为常数

2、除留取余法:关键值除以比散列表长度小的素数所得的余数作为散列地址。Hash(key) = key % p;

3、平均取中法:先计算构成关键码的标识符的内码的平方,然后按照散列表的大小取中间的若干位作为散列地址。

4、折叠法:把关键码自左到右分为位数相等的几部分,每一部分的位数应与散列表地址位数相同,只有最后一部分的位数可以短一些。把这些部分的数据叠加起来,就可以得到具有关键码的记录的散列地址。分为移位法和分界法。

5、随机数法:选择一个随机函数,取关键字的随机函数作为它的哈希地址。

6、数学分析法:设有N个d位数,每一位可能有r种不同的符号。这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布均匀些,每种符号出现的机会均等;在某些位上分布不均匀,只有某几种符号经常出现。可根据散列表的大小,选取其中各种符号分布均匀的若干位作为散列地址。

但是无论我们怎么样去选择这个函数,都不可能完全避免不同key值的hash[value]​指向会映射到同一散列地址上。这样就会造成哈希冲突/哈希碰撞。所以我们需要找到处理这种冲突的方法,大概分为这两种:分离链接法和开放定址法。

分离链接法:其实就是我们说的hash桶的含义了。哈希桶就是盛放不同key链表的容器(即是哈希表),在这里我们可以把每个key的位置看作是一个桶,桶里放了一个链表

相信大家可以看出来,使用一个数组来存放记录方法的哈希冲突太多,基于载荷因子的束缚,空间利用率不高,在需要节省空间的情况下,我们可以用哈希桶来处理哈希冲突。

哈希桶是使用一个顺序表来存放具有相同哈希值的key的链表的头节点,利用这个头节点可以找到其它的key。

 

这里写图片描述

 

 

 

 

struct Hashdata{    int key;    int value;    struct Hashdata *next;};typedef struct{    struct Hashdata **head;    int ht_width;}Hashtable;struct Hashdata * hashaddr(Hashtable * ht, int key){    /*map 公式, 负数abs转成正的来计算。 取相同计算结果的链表*/    int k = abs(key) % ht->ht_width;    return ht->head[k];}struct Hashdata * hashfind(struct Hashdata *hdata, int key){    while(hdata){        if(hdata->key == key){            //printf("cyx find key=%d\n",key);            return hdata;        }        if(hdata->next)            hdata = hdata->next;        else            return NULL;    }    return NULL;}int hashinsert(int key, int val, Hashtable * ht){    struct Hashdata * hdata =(struct Hashdata *) malloc(sizeof(struct Hashdata));    struct Hashdata * haddr = hashaddr(ht, key);    int k;    hdata->key = key;    hdata->value = val;    hdata->next = NULL;    if(haddr == NULL){        /*桶为空,则为链表头*/        k = abs(key)% ht->ht_width;        ht->head[k] = hdata;    }else{        /*桶有节点,则加入链表末尾*/        while(haddr->next)            haddr = haddr->next;        /*新alloc的加入链表尾部*/        haddr->next = hdata;    }    return 0;}int hashinit(Hashtable *ht, int width){        ht->head = (struct Hashdata **)malloc(width * sizeof(struct Hashdata *));    memset(ht->head, 0, width * sizeof(struct Hashdata *));    ht->ht_width = width;    return 0;}int* twoSum(int* nums, int numsSize, int target, int* returnSize){    int i, key;        Hashtable ht;    struct Hashdata *hdata;    hashinit(&ht, numsSize);    for(i=0;i
value != i){ // printf("cyx %d %d\n",nums[i], nums[hdata->value] ); nums[0] = i; nums[1] = hdata->value; *returnSize = 2; return nums; } } *returnSize = 0; return nums;}

 

 

 

 

 

 

 

你可能感兴趣的文章
AT Command for QOS
查看>>
中文字号VS英文字号(磅)VS像素值的对应关系
查看>>
关于@override报错的问题
查看>>
Linux中禁止Ctrl-Alt-Delete
查看>>
概念辨析:dBm, dBi, dBd, dB, dBc, dBuV
查看>>
麻雀虽小,五脏俱全:新新手,IP和Socket小知识
查看>>
Windows常用命令集锦
查看>>
MMS彩信是怎么炼成地(一) 编辑
查看>>
MMS是怎样炼成的(二)封装
查看>>
SMIL 参考手册
查看>>
分析pptpd程序中关于执行pptpd和pppd程序的部分源代码
查看>>
RFC 1180 - TCP/IP tutorial 学习笔记
查看>>
HOWTO: Unpack, Edit, and Re-Pack Boot Images
查看>>
ramfs, rootfs & initramfs
查看>>
Tom's attempts to get GPRS working over bluetooth with his laptop
查看>>
Connecting to GPRS over Bluetooth on Linux
查看>>
Linux网络资源
查看>>
Android对Kernel的改动汇总
查看>>
WGET 通过代理下载
查看>>
JITTER BUFFER
查看>>