十.散列和Hashtable类
散列概述
储存在数组内的每一个数据项都是基于一些数据块,这数据块被称为键。
把键映射到一个范围从0到散列大小的数上,这需要散列函数完成。
散列函数的理想目标是把自身单元内的每一个键都存储到数组内,但可能会出现两个键散列到相同数值的情况,这现象叫做冲突。
散列的主要问题是:
1)确定用多大维数的数组作为散列表?原则:建议数组的大小是一个素数
2)如何选择散列函数?根据所用键的数据类型,把键尽可能平均地分不到数组的单元内。
3)如何解决冲突?确定适当数组大小的策略,基于如何解决冲突。
散列表中元素数量与表大小的比率被称为负载系数。负载系数为1.0时,散列表的性能最佳。
选择散列函数
选择数组大小的时候,一个重要的原则就是要选择素数。
10007是素数,而且他没有大到会使用大量的内存来降低程序的内存。
下面例子中,散列函数SimpleHash利用霍纳(Horner)法则来计算(关于37的)多项式函数。
string[] names = new string[10007];
string name;
string[] somenames = new string[]
{ "David", "Jennifer", "Donnie","Mayo",
"Raymond","Bernica","Mike", "Clayton","Beata","Michael"};
int hashVal;
for (inti = 0; i < 10; i++)
{
name = somenames[i];
hashVal = SimpleHash(name, names);
names[hashVal] = name;
}
ShowDistrib(names);
///337 Bernica
///3631 David
///4395 Donnie
///4929 Mayo
///4943Jennifer
///5219 Mike
///8094 Raymond
///8547Michael
///8942 Beata
///9237Clayton
private intSimpleHash(string s, string[]arr)
{
int tot = 0;
char[] cname;
cname = s.ToCharArray();
for (inti = 0; i < cname.GetUpperBound(0); i++)
{
tot += 37 * tot + (int)cname[i];
}
tot = tot % arr.GetUpperBound(0);
if (tot < 0)
{ tot += arr.GetUpperBound(0); }
return (int)tot;
}
static voidShowDistrib(string[] arr)
{
for (inti = 0; i < arr.GetUpperBound(0); i++)
{
if (arr[i] != null)
{ Console.WriteLine(i+" "+arr[i]); }
}
}
查找散列表中数据
在散列表中查找数据,需要计算键的散列值,然后访问数组中的对应元素。
这样的查找方式很明显要比逐个查找要效率的多。
解决冲突
桶式散列法
桶,是一种存储在散列表元素内的简单数据结构 ,它可以存储多个数据项。大多数实现中,这种数据结构就是一个数组,例如ArrayList类。
桶式散列法,最终要的事情,就是保持所用数组的数量尽可能地少。
插入一个数据项
用散列函数来确认用哪个数组(散列键)来存储数据项,然后查看此数据项是否已经在数组内。如果存在,就不做。如果不存在,就将数据项添加进这个数组。
移出一个数据项
用散列函数来确认用哪个数组(散列键)来移出数据项,然后查看此数据项是否已经在数组内。如果存在,就移出数据项。如果不存在,就不做。
开放定址法
开放定址函数会在散列表数组内寻找空单元来防止数据项。
两种不同的开放定址策略:
线性探查
采用线性函数来确认试图插入的数组单元。顺次尝试单元直到找到一个空单元为止。
会出现的问题是数组内相邻单元中的数据元素会趋近成聚类,从而使得后续空单元的探查时间变得更长且效率更低。
平方探查
平方函数来确认要尝试哪个单元。
平方探查法的有趣属性是在散列表空余单元少于一半的情况下总能保证找到空的单元。
双重散列法
两个条件:散列函数不应该曾经计算到0,;表的大小必须是素数
Hashtable类
常见的应用之一,就是构造术语表或者术语词典。
可以为键的数据类型指定散列函数或者使用内置的函数。
避免冲突的策略是桶的思想。
namespace System.Collections
public class Hashtable : IDictionary,ICollection, IEnumerable,ISerializable, IDeserializationCallback,ICloneable
构造器
其中一个有代表性的声明方式:
//
// 摘要:
// 使用指定的初始容量、指定的加载因子、默认的哈希代码提供程序和默认比较器来初始化 System.Collections.Hashtable类的新的空实例。
//
// 参数:
// capacity:
// System.Collections.Hashtable对象最初可包含的元素的近似数目。
//
// loadFactor:
// 0.1 到 1.0 范围内的数字,再乘以提供最佳性能的默认值。结果是元素与存储桶的最大比率。
//
// 异常:
// System.ArgumentOutOfRangeException:
// capacity 小于零。- 或 -loadFactor小于 0.1。-或 -loadFactor 大于 1.0。
//
// System.ArgumentException:
// capacity 导致溢出。
public Hashtable(intcapacity,float loadFactor);
代码:
Hashtable g = new Hashtable();///默认容量,默认负载系数。capacity:0.72
Hashtable g1 = new Hashtable(50);///50个元素容量,默认负载系数。capacity:0.72
Hashtable g2 = new Hashtable(25,0.3F);///25个元素且负载系数是0.3。capacity:0.216000021
检索键和数值
Keys、Values方法返回一个Enumerator对象。
foreach (varitemin g.Keys)
{
}
foreach (varitemin g.Values)
{
}
检索基于键的数值
对散列表使用索引,索引为键名。
foreach (varitemin g.Keys)
{
Console.Write(g[item]);
}
其他常用方法
Count属性,散列表内元素的数量
Clear方法,清除散列表内容的方法
ContainKey方法(ContainValue方法),判断散列表内是否含指定键(和数值)的方法
Remove方法,从散列表中移除元素
CopyTo方法,散列表元素赋值到数组