对哈希表的小见——再散列(嵌套)
2014-10-18 00:12
113 查看
最近公司要开发一个快速查询和查看的模块,算法的复杂度不能与n值有关,也就是固定的、可预测的区间范围,即是这个搜索不以比较为依据进行。
快速索引,当然便想到哈希表(以前听到哈希表都惧怕,怕的就是冲突的问题),为了完成这个任务,还是只得选择在CSDN上搜索哈希表的相关知识。哈希表,是根据关键码值(Key value)而直接进行访问的数据结构(不了解的可以访问:http://baike.baidu.com/view/329976.htm?fr=aladdin、http://zh.wikipedia.org/wiki/%E5%93%88%E5%B8%8C%E8%A1%A8)。解决冲突的方法有:开放寻址法、再散列法、链地址法、建立公共缓冲区。以下解决冲突的四种方法:
链地址法:将所有关键字为同义词的记录存储在同一线性链表中。
开放寻址法:Hi=(H(key)+di)MOD m i=1,2,...,k(k<=m-1),其中m为表长,di为增量序列。如果di值可能为1,2,3,...m-1,称线性探测再散列;如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2) 称二次探测再散列;如果di取值可能为伪随机数列,称伪随机探测再散列。例:在长度为11的哈希表中已填有关键字分别为17,60,29的记录,现有第四个记录,其关键字为38,由哈希函数得到地址为5,若用线性探测再散列,如下:
再哈希法:当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。
建立公共溢出区:假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录。
这里最简单,最常用的应该是链地址法,教程举例都是这样。
再说项目,该功能是使用大小为13个字节的数据作为关键字构建哈希表。13个字节,好大的数字2^104 = 1.0141204801825835e+31(^表示幂)呢。采用链地址法?网上也只能搜到这样的实例。这样的话,在解决一次冲突之后使用了链表,当然不合符要求。而且这个解决一次冲突的映射关系也是个非常苦恼的事情。这个想法在头脑里固定了一段时间,想想实际要处理的数据情形——同一时间段重复的数据比较多。那能不能细分?能。
一个字节最大表示的个数是255,将关键字拆分按字节计算,就用这个256数据作为关键字构建哈希表。数据只在0-255之间,那么我的第一个冲突就很好地解决了。哈希表的成员所指向的,如果是另一张子表,子表也0-255之间;只是最后一张表是尾表,里面的表项不是子表,而是数据。这样,由于关键字的长度固定,数据都在最后一层表里。这样构成了一棵很好的哈希树。树结构图如下:
(上图表示的是按5个字节的关键字存储的数据,关键字分别为2 4 9 255 4[0x020409FF04]、2 255 7 69[0x02FF070609]、9 7 8 4 255[0x09070804FF],0x表示十六进制)
当然这些表是动态建立的,如果数据来得很零散,就会造成哈希表所开僻的空间越大。对于13字节的关键来说,开发的平台上是128GB的内存,表开满了所占内存相当大(2^(13 * 8)、2^104字节,2^74 GB,注:1G=2^30),128GB内存也是海之一粟。所以,该算法只在数据关键字比较集中的时候可用。
测试:平台为64位128GB内存的linux系统,开启20GB表缓存[哈希表预先分配空间,未加入哈希树],关键字递增的方法进行,结果是0.022秒/百万条(0.00144秒/65536条) 插入数据速率,搜索速率更快,因为搜索过程少了分配空间的操作。
还有,在这个问题中,也存在表缓存不够用的情况,加入删除表机制和缓存管理机制最好,删除表机制用于释放哈希树中需要释放的树节点,缓存管理表用来管理每一块标记为释放的表(在释放的空间上连接这些释放管理表,那就没有多余的内存消耗啦)。
该哈希表在C代码实现数据结构如下:
以上就是最近在工作中得到的关于哈希表的经验。还是一句话:具体情况具体而定,以唯物观点看问题。
快速索引,当然便想到哈希表(以前听到哈希表都惧怕,怕的就是冲突的问题),为了完成这个任务,还是只得选择在CSDN上搜索哈希表的相关知识。哈希表,是根据关键码值(Key value)而直接进行访问的数据结构(不了解的可以访问:http://baike.baidu.com/view/329976.htm?fr=aladdin、http://zh.wikipedia.org/wiki/%E5%93%88%E5%B8%8C%E8%A1%A8)。解决冲突的方法有:开放寻址法、再散列法、链地址法、建立公共缓冲区。以下解决冲突的四种方法:
链地址法:将所有关键字为同义词的记录存储在同一线性链表中。
开放寻址法:Hi=(H(key)+di)MOD m i=1,2,...,k(k<=m-1),其中m为表长,di为增量序列。如果di值可能为1,2,3,...m-1,称线性探测再散列;如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2) 称二次探测再散列;如果di取值可能为伪随机数列,称伪随机探测再散列。例:在长度为11的哈希表中已填有关键字分别为17,60,29的记录,现有第四个记录,其关键字为38,由哈希函数得到地址为5,若用线性探测再散列,如下:
再哈希法:当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。
建立公共溢出区:假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录。
这里最简单,最常用的应该是链地址法,教程举例都是这样。
再说项目,该功能是使用大小为13个字节的数据作为关键字构建哈希表。13个字节,好大的数字2^104 = 1.0141204801825835e+31(^表示幂)呢。采用链地址法?网上也只能搜到这样的实例。这样的话,在解决一次冲突之后使用了链表,当然不合符要求。而且这个解决一次冲突的映射关系也是个非常苦恼的事情。这个想法在头脑里固定了一段时间,想想实际要处理的数据情形——同一时间段重复的数据比较多。那能不能细分?能。
一个字节最大表示的个数是255,将关键字拆分按字节计算,就用这个256数据作为关键字构建哈希表。数据只在0-255之间,那么我的第一个冲突就很好地解决了。哈希表的成员所指向的,如果是另一张子表,子表也0-255之间;只是最后一张表是尾表,里面的表项不是子表,而是数据。这样,由于关键字的长度固定,数据都在最后一层表里。这样构成了一棵很好的哈希树。树结构图如下:
(上图表示的是按5个字节的关键字存储的数据,关键字分别为2 4 9 255 4[0x020409FF04]、2 255 7 69[0x02FF070609]、9 7 8 4 255[0x09070804FF],0x表示十六进制)
当然这些表是动态建立的,如果数据来得很零散,就会造成哈希表所开僻的空间越大。对于13字节的关键来说,开发的平台上是128GB的内存,表开满了所占内存相当大(2^(13 * 8)、2^104字节,2^74 GB,注:1G=2^30),128GB内存也是海之一粟。所以,该算法只在数据关键字比较集中的时候可用。
测试:平台为64位128GB内存的linux系统,开启20GB表缓存[哈希表预先分配空间,未加入哈希树],关键字递增的方法进行,结果是0.022秒/百万条(0.00144秒/65536条) 插入数据速率,搜索速率更快,因为搜索过程少了分配空间的操作。
还有,在这个问题中,也存在表缓存不够用的情况,加入删除表机制和缓存管理机制最好,删除表机制用于释放哈希树中需要释放的树节点,缓存管理表用来管理每一块标记为释放的表(在释放的空间上连接这些释放管理表,那就没有多余的内存消耗啦)。
该哈希表在C代码实现数据结构如下:
<pre name="code" class="cpp">// 一个字节表示的最大个数 #define BIT_EXPRESS_MAXVALUE (256) // 哈希表数据结构 typedef struct s_hashtable { union { // 中间表数据类型 struct <span style="font-family: Arial, Helvetica, sans-serif;">s_hashtable *child</span><span style="font-family: Arial, Helvetica, sans-serif;">[BIT_EXPRESS_MAXVALUE];</span> // 尾表数据类型 unsigned char *data[BIT_EXPRESS_MAXVALUE]; }t; }SHashTree; // 非递归遍历使用的栈结构 typedef struct s_stack { // 入栈的哈希表指针 SHashTree *table; // 入栈时指定的哈希表成员索引 int index; }SStack;
以上就是最近在工作中得到的关于哈希表的经验。还是一句话:具体情况具体而定,以唯物观点看问题。
相关文章推荐
- 关于Python中的字典(映射、哈希、散列)理解
- 字符串哈希之散列表处理冲突 poj1880
- 哈希之开散列-哈希桶
- SQL Server执行计划那些事儿(1)——哈希、合并、嵌套联接的选择
- hash(哈希,散列)
- 哈希之开散列
- 字典、哈希与Map(嵌套map非常管用)
- SQL Server中的三种物理连接操作:嵌套循环连接、合并连接、哈希匹配
- 第六章——根据执行计划优化性能(1)——理解哈希、合并、嵌套循环连接策略
- 6. 哈希。哈希函数的有哪些种? 处理冲突的方法? 闭散列方法有哪些?
- SQL执行计划-嵌套连接、哈希连接和合并连接
- SQL Server 三大物理连接算法(嵌套,合并,哈希)的IO成本总结
- 第六章——根据执行计划优化性能(1)——理解哈希、合并、嵌套循环连接策略
- java 解决Hash(散列)冲突的四种方法--开放定址法(线性探测,二次探测,伪随机探测)、链地址法、再哈希、建立公共溢出区
- 哈希(散列)的分离链接法
- 表连接三剑客(嵌套循环连接,哈希连接,排序合并连接)
- 动态散列(哈希)待续
- 第六章——根据执行计划优化性能(1)——理解哈希、合并、嵌套循环连接策略
- 【数据结构----笔记2】查找算法之【哈希查找或散列查找】
- Hash(哈希或散列)知识概貌