算法总结系列之八:复读机的故事 - 散列表.NET应用的研究(下集)
估计写这么个题目会被扔鸡蛋, 因为实在是太大了. 各位不要期望太高啊,我写这东西,就是为了给自己个备忘. 你们要是把它当垃圾看, 说不定还能发现点什么东西.
言归正题. 说实话, .NET Framework的实现可能比我们认为的要好一些, 比如线程安全, 代码效率, 甚至以及代码风格方面. 比如HashTable 的实现就是一个比较好的佐证.
上文说到, .NET Framework 中的哈希表, 使用了双重散列的方式来计算散列值. 那么到底精确的来说, 是采用了什么关系式呢? 实际上很简单, 这个关系式就是:
h(key, n) = h1(key) + n*h2(key) |
其中
h1(key) = GetHash(key); |
h2(key) = 1 + ((h1(key) >> 5) + 1) % (hashsize - 1); |
key - 待散列的关键字值,
n - 发生碰撞的次数,
hashsize - 哈希表的容量,是一个素数.大牛Knuth同学在他那本<Art of Computer Programming>说过为什么要使用素数. .NET Framwork 兼顾效率和范围, 给出了这样一个实现:
internal static readonly int[] primes = {
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369};
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
internal static int GetPrime(int min)
{
if (min < 0)
throw new ArgumentException(Environment.GetResourceString("Arg_HTCapacityOverflow"));
for (int i = 0; i < primes.Length; i++)
{
int prime = primes[i];
if (prime >= min) return prime;
}
//outside of our predefined table.
//compute the hard way.
for (int i = (min | 1); i < Int32.MaxValue;i+=2)
{
if (IsPrime(i))
return i;
}
return min;
}
这段代码就是说,你要的容量如果在7199369之内,就从这个表里面给你返回一个最接近的素数; 否则, 给你找一个比想要容量大一点点的素数返回.
GetHash( Object ) - 是返回参数对象哈希值的函数, 它默认等于每个对象的GetHashCode()函数, 除非你制定了特定的IComparer对象,那么它会返回该Comparer的哈希值.哦? 原来GetHashCode()是用在这里啊... 我们知道每一个起源于Object的类,都会给IEqualityComparer接口一个默认的实现,这个接口的两个方法就是Equals()和GetHashCode().你当然可以重写GetHashCode()函数的实现, 但是这个函数直接关系到了这个类的对象放入HashTable以后, HashTable的效率表现. 默认的类方法GetHashCode()不至于太差, 如果你要实现自己的GetHashCode方法, 那么一定要考虑一下会不会和别的类生成的HashCode容不容易落入同一个值区间内. <Effective C#>这本书的第十个条目就解释了这样一个规律:
对于引用类型自带的GetHashCode函数来说,基本上是正确的,但是效率不高;而对于值类型自带的GetHashCode函数而言,基本上是不正确的,即使正确也是效率不高。如果重写类型的GetHashCode函数,想要达到既正确又高效是不可能的。
当然, 还有一条建议:
不建议重写此函数,而且在使用这个函数也需要先注意它的实现
每个哈希表都有装载率,装载率越高,空间越节省, 装载率越低, 查找越快. .NET Framework的HashTable默认装载率是0.72, 但是表面上看来是1.0 . .NET的开发人员似乎为了引导我们使用他认为效率最为平衡的装载率, 偷偷的做了一下弊, 呵呵
public Hashtable(int capacity, float loadFactor) {
if (capacity < 0)
throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (!(loadFactor >= 0.1f && loadFactor <= 1.0f))
throw new ArgumentOutOfRangeException("loadFactor", Environment.GetResourceString("ArgumentOutOfRange_HashtableLoadFactor", .1, 1.0));
// Based on perf work, .72 is the optimal load factor for this table.
double rawsize = capacity / this.loadFactor;
if (rawsize > Int32.MaxValue)
throw new ArgumentException(Environment.GetResourceString("Arg_HTCapacityOverflow"));
// Avoid awfully small sizes
int hashsize = (rawsize > 11) ? HashHelpers.GetPrime((int)rawsize) : 11;
buckets = new bucket[hashsize];
loadsize = (int)(this.loadFactor * hashsize);
isWriterInProgress = false;
// Based on the current algorithm, loadsize must be less than hashsize.
BCLDebug.Assert( loadsize < hashsize, "Invalid hashtable loadsize!");
}
接下来的问题是, 我能计算出h(key, n)的值了, 然后这个元素会被放到哪里? 哈希表内部维护了一个key-value-hash code结构数组buckets, 每一个元素根据计算出的h(key, 1)值seed通过关系式(int) (seed % (uint)buckets.Length) 得到对应的数组索引, 如果发生碰撞则计算h(key, 2), 不碰撞就存入,依次类推.
针对容量不足和多次添加删除之后表内结构混乱, HashTable还提供了 expand() 和 rehash() 方法, 有兴趣的同学可以看看, 有点意思.
转载于:https://www.cnblogs.com/sun/archive/2008/08/01/1258008.html
算法总结系列之八:复读机的故事 - 散列表.NET应用的研究(下集)相关推荐
- 【算法导论】学习笔记——第11章 散列表
11.1 直接寻址表 当关键字的全域U很小,可采用直接寻址的方式.假设动态集合S的元素都取自全域U={0, 1, ..., m-1}的一个关键字,并且没有两个元素具有相同的关键字. 为表示动态集合,使 ...
- 算法快学笔记(五):散列表
1. 介绍 当需要根据给定的值需要快速得到想要值的时候,散列表是一个非常有用的数据结构,假设你在一家杂货店上班.有顾客来买东西时,你得在一个本子中查 找价格,如果本子的内容不是按字母顺序排列的,你可以 ...
- 死磕算法第一弹——数组、集合与散列表
本文整理来源 <轻松学算法--互联网算法面试宝典>/赵烨 编著 数组 自我解读 数组是一堆数据按照顺序放入的固定长度空间. 数组的长度固定,所以在声明时需要指定数组长度.如果长度不够用,也 ...
- 简单的程序诠释C++ STL算法系列之八:mismatch
C++STL的非变易算法(Non-mutating algorithms)是一组不破坏操作数据的模板函数,用来对序列数据进行逐个处理.元素查找.子序列搜索.统计和匹配. mismatch算法是比较两个 ...
- BP算法双向传,链式求导最缠绵(深度学习入门系列之八)
摘要: 说到BP(Back Propagation)算法,人们通常强调的是反向传播,其实它是一个双向算法:正向传播输入信号,反向传播误差信息.接下来,你将看到的,可能是史上最为通俗易懂的BP图文讲解, ...
- 【牛客 - 327G】处女座与复读机(可编辑距离问题,dp)
题干: 链接:https://ac.nowcoder.com/acm/contest/327/G 来源:牛客网 一天,处女座在牛客算法群里发了一句"我好强啊",引起无数的复读,可是 ...
- (江西财经大学第二届程序设计竞赛同步赛)E-是不是复读机
E-是不是复读机 题目描述: 在复读纪元2140年,复读机(们)已经放弃了如下所示的低级复读方式: "哟,小伙汁,想不到你也是个复读机" "哟,小伙汁,想不到你也是个复读 ...
- 敏捷开发用户故事系列之一:何为用户故事
这是敏捷开发用户故事系列的第一篇.(之一,之二,之三,之四,之五,之六,之七,之八,之九) 全系列将涉及何为用户故事,面向客户价值编写故事,用户建模,产品待开发项的分类,故事颗粒度,故事的组织结构,等 ...
- 国密SM9算法C++实现之八:密钥交换算法
SM9算法C++实现系列目录: 基于JPBC的SM9算法的java实现与测试 国密SM9算法C++实现之0:源码下载地址 国密SM9算法C++实现之一:算法简介 国密SM9算法C++实现之二:测试工具 ...
最新文章
- 关于微信红包的架构思考
- 【ARM】Tiny4412裸板编程之MMU(段 16M)
- inline函数和一般的函数有什么不同
- 此上下文中不允许函数定义。_面试官:那我们来说说执行上下文吧
- avg最多用多少列 mysql_MySQL_MySQL中几种数据统计查询的基本使用教程,统计平均数
SELECT AVG() FROM 语 - phpStudy...
- asp.net core 官方文档
- 35岁程序员失业后感慨:之前月薪2万,现在找5千的工作都没人要
- 面试总结——Java篇
- 使用xcap进行更改报文并进行回放以及回放报文只能看到请求流量看不到响应流量的问题
- 聚类分析在SPSS上的实现及分析
- java opts tomcat,jvm初学篇-tomcat JAVA_OPTS配置
- 学计算机有那些方向,计算机专业的研究生研究方向有哪些
- win7 正式版安装成功,贴图得瑟一下
- Power Query批量合并Excel文件
- xctf攻防世界 MISC高手进阶区 3-11
- 求解圆圈中最后剩下的数字
- PTA 天梯赛 L1-7 天梯赛的善良 (20 分)
- 【Database-02】达梦数据库 - DM Manager管理工具安装
- java导出excel问题记录
- linux多系统引导管理,Linux 多重引导MBR与系统引导管理器GRUB.docx