散列方法的主要思想是根据节点的关键码来确定其存储位置:以关键码值K为自变量,通过一定的函数关系h(K)(该函数关系称为散列函数),计算出对于的函数值,把这个值解释为节点的存储地址,将节点存入到此存储单元中。检索时,用同样的方法计算地址,然后得到相应的单元里去取要找的结点。通过散列方法可以对结点进行快速检索。
散列(hash,也称“哈希”)是一种重要的存储地址,也是一种常见的检索方法。
按散列存储方式构造的存储结构称为散列表(hash table)。散列表中的一个位置称为槽(slot)。散列技术的核心是散列函数(hash function)。对任意给定的动态查找表DL,如果选定了某个“理想的”散列函数h及相应的散列表HT,则对DL中的每个数据元素X。函数值h(X.key)就是X在散列表HT中的存储位置。插入(或建表)时数据元素X将被安置在该位置上,并且检索X时也到该位置上去查找。由散列函数决定的存储位置称为散列地址。因此,散列的核心就是:由散列函数决定关键码值(X.key)与散列地址h(X.Key)之间的对应关系,通过这种关系实现组织存储并检索。
一般情况下,散列表的存储空间是一个一位数组HT[M],散列地址是数组的下标。涉及散列方法的目标,就是设计某个散列函数h,0<=h(K)(指散列地址)<M;对于关键码值K,得到HT[i]=K。在一般情况下,散列表的控件必须比结点的集合大,此时虽然浪费了一定的空间,但换取的是检索效率。设散列表的空间大小为M,填入表中的结点数为N,则称为散列表的负载因子(Load factor,也有人翻译为“装填因子”)。建立散列表时,若关键码与散列地址是一对一的关系,则在检索时只需根据散列函数给定某种运算,即可得到待查结点的存储位置。但是,散列函数可能对于两个不想等的关键码计算出相同的散列地址,我么称该现现象为冲突,发生冲突的两个关键码称为该散列函数的同义词。在实际应用中,很少存在不产生冲突的散列函数,我们必须考虑在冲突发生时的处理方法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestDemo10
{
class Program
{
static void Main(string[] args)
{
//选择数组大小很重要的一点就是选择素数,10007也不会大到占用太多内存而降低程序占用的内存
string[] names = new string[10007];
string name;
string[] someNames = new string[] { "name1", "name2", "name3", "name4", "name5", "name6", "name7", "name8", "name9", "name10" };
int hashVal;
for (int i = 0; i < 10; i++)
{
name = someNames[i];
//hashVal = SimpleHash(name, someNames);
InHash(name, someNames);
//names[hashVal] = name;
}
ShowDistrib(names);
Console.Read();
}
static int SimpleHash(string s, string[] arr)
{
int tot = 0;
char[] cname;
cname = s.ToCharArray();
//数组.GetUpperBound(0)得到的值与数组.Length得到的值相差一个长度。
/*
* GetUpperBound(0)方法得到第一个纬度的索引的上限
* Length得到的是该纬度索引的长度
* 一般来说,GetUpperBound(0)=Length-1
* */
int cNameLength = cname.GetUpperBound(0);
int cNamecount = cname.Length;
for (int i = 0; i <= cname.GetUpperBound(0); i++)
{
tot += (int)cname[i];
}
return tot % arr.GetUpperBound(0);
}
static void ShowDistrib(string[] arr)
{
for (int i = 0; i < arr.GetUpperBound(0); i++)
{
if (arr[i] != null)
{
Console.WriteLine(i + " " + arr[i]);
}
}
}
/// <summary>
/// 这个函数利用霍尔法则来计算多项式函数(关于37)。
/// </summary>
/// <param name="s"></param>
/// <param name="arr">集合</param>
/// <returns></returns>
static int BetterHash(string s, string[] arr)
{
long tot = 0;
char[] cname;
cname = s.ToCharArray();
for (int i = 0; i <= cname.GetUpperBound(0); i++)
{
tot += 37 * tot + (int)cname[i];
}
int arrLength = arr.GetUpperBound(0);
tot = tot % arr.GetUpperBound(0);
if (tot < 0)
{
tot += arr.GetUpperBound(0);
}
return (int)tot;
}
static bool InHash(string s, string[] arr)
{
int hval = BetterHash(s, arr);
if (arr[hval] == s)
{
return true;
}
else
{
return false;
}
}
}
}
散列函数:
在以下的讨论中,我们假设处理的值是为整型的关键码,否则我们总可以建立一种关键码与正整数之间的对应关系,从而把关键码的检索转化为对与其对于的正整数的检索;同时,进一步假定散列函数的值落在0到M-1之间。散列函数的选取原则是:
- 除余法
顾名思义,除余法就是关键码x除以M(往往取散列表长度),并取余数作为散列地址。除余法几乎是最简单的散列方法,烦列函数为:h(X)=X mod M; - 乘余取整法
先让关键码K乘一个常数A(0<A<1),提取乘积的小数部分。然后用整数n乘这个值,对结果向下取整。把它作为散列的地址。散列函数为:hash(key)=_LOW(n*(A*Key%1))。其中,“A × key % 1”表示取 A × key 小数部分,即: A × key % 1 = A × key - _LOW(A × key), 而_LOW(X)是表示对X取下整。 - 平方取中法
由于整数相除的运行速度通常比相乘要慢,所以有意识地避免使用除余法运算可以提高散列算法的运行时间。平方取中法的具体实现是:先通过求关键码的平方值,从而扩大相近数的差别,然后根据表长度取中间的几位数(往往取二进制的比特位)作为散列函数值。因为一个乘积的中间几位数与乘数的每一数位都相关,所以由此产生的散列地址较为均匀。
- 数字分析法
设有 n 个 d 位数,每一位可能有 r 种不同的符号。这 r 种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布均匀些,每种符号出现的几率均等; 在某些位上分布不均匀,只有某几种符号经常出现。可根据散列表的大小,选取其中各种符号分布均匀的若干位作为散列地址。
- 基数转换法
将关键码值看成另一种进制的数再转换成原来进制的数,然后选其中几位作为散列地址。
- 折叠法
有时关键码所含的位数很多,采用平方取中法计算太复杂,则可将关键码分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为散列地址,这方法称为折叠法。
- ELFHash字符串散列函数
ELFhash函数在UNIX系统V 版本4中的“可执行链接格式”( Executable and Linking Format,即ELF )中会用到,ELF文件格式用于存储可执行文件与目标文件。ELFhash函数是对字符串的散列。它对于长字符串和短字符串都很有效,字符串中每个字符都有同样的作用,它巧妙地对字符的ASCII编码值进行计算,ELFhash函数对于能够比较均匀地把字符串分布在散列表中。http://blog.chinaunix.net/uid-24683784-id-3061386.html(ELFhash,字符串哈希函数)
10.3 查找散列表中数据
为了在散列表中查找数据,需要计算关键字的散列值,然后访问数组中的对于的元素。
static bool Inhash(string s,string [] arr){
int hval=Betterhash(s,arr);
if(arr[hval]==s)
return true;
else
return false;
}
10.4 解决冲突
产生冲突原因:在初始定义散列列表时,声明倾向只有一个数据值存在散列表元素内,如果没有冲突,那么会顺利进行,但是如果散列函数为两个数据项返回了相同的数据,那么就有问题了。
1、桶式散列法
桶是一种存储在散列表元素内的简单数据结构,它可以存储多个数据项,这里的实现会用ArrayList,它会允许运行超出范围而且允许分配更多的空间,这种方法数据结构会使实现更加高效。
插入一个数据项,首先要用散列函数确定哪一个ArrayList用来存储数据项,然后查看是否存在,若不存在则调用add方法添加,反之不管。移除一个数据项首先确定散列值,并且转到对应的ArrayList,查看ArrayList并确定是否存在,存在则移除。
当使用桶式散列法,尽量保持所用的ArrayList数量尽肯能的少。这样会在添加或移除时减少所需额外工作。一旦有了冲突,ArrayList容量会变为2,每次容量满了会扩大两倍。
2、开放定址法
开放定址函数会在散列数组内寻找单元来放置数据项。如果尝试的第一个单元是满的,那么就尝试下一个,直至找到一个空单元为止。开放定址策略有两种:线性探查法和平方探查法。
线性探查法:线性探查法用线性函数来试图插入数组单元,这就意味着需要找到一个空单元。线性探查法的问题是数组内相邻单元的数据元素会趋近聚类(将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类)。http://www.nowamagic.net/academy/detail/3008050(转)
平方探查法:等待补充...
开放定址函数会在散列数组内寻找单元来放置数据项。如果尝试的第一个单元是满的,那么就尝试下一个,直至找到一个空单元为止。开放定址策略有两种:线性探查法和平方探查法。
线性探查法:线性探查法用线性函数来试图插入数组单元,这就意味着需要找到一个空单元。线性探查法的问题是数组内相邻单元的数据元素会趋近聚类(将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类)。http://www.nowamagic.net/academy/detail/3008050(转)
平方探查法:等待补充...
开放定址函数会在散列数组内寻找单元来放置数据项。如果尝试的第一个单元是满的,那么就尝试下一个,直至找到一个空单元为止。开放定址策略有两种:线性探查法和平方探查法。
线性探查法:线性探查法用线性函数来试图插入数组单元,这就意味着需要找到一个空单元。线性探查法的问题是数组内相邻单元的数据元素会趋近聚类(将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类)。http://www.nowamagic.net/academy/detail/3008050(转)
平方探查法:等待补充...
10.5 HashTable类
HashTable类是字典对象的一种特殊类型,其中的数值都是在源于关键字的散列代码的基础上进行存储的。这个类用来避免冲突的策略就是桶的思想。桶是具有相同散列代码的对象的虚拟组合,如果两个关键字都具有相同的散列代码,那么就把他们放置在同一个桶内,否则就把每一个具有唯一散列代码关键字放置在他自己的桶内。
用一个HashTable对象内的桶的数量被称为是负载系数。负载系数是元素与桶数量之间的比率。此系数初始为1.0.当实际系数达到初始系数的时候,就把负载系数增加成一个最小的素数,这个最小素数是当前桶数量的两倍。负载系数很重要,系数越小,HashTable对象的性能就越好。
HashTable类的添加数据和取值:
class Program { static void Main(string[] args) { Hashtable hTable = new Hashtable(25); hTable.Add("salayr",100000); hTable.Add("name", "Tom"); hTable.Add("age",44); hTable.Add("dept","information technology"); hTable["sex"] = "Male";//不存在即会添加 hTable["age"] = 45;//存在即会修改 Console.WriteLine("The Keys is:"); foreach (var Keys in hTable.Keys) { Console.WriteLine(Keys); } Console.WriteLine("The Values is:"); foreach (var Values in hTable.Values) { Console.WriteLine(Values); }
Console.Read(); } }Console.WriteLine("The key -- Values is:"); foreach (var Keys in hTable.Keys) { Console.WriteLine(Keys+";"+hTable[Keys]); }
部分摘录自
http://blog.csdn.net/koself/article/details/7869453
开放定址函数会在散列数组内寻找单元来放置数据项。如果尝试的第一个单元是满的,那么就尝试下一个,直至找到一个空单元为止。开放定址策略有两种:线性探查法和平方探查法。
线性探查法:线性探查法用线性函数来试图插入数组单元,这就意味着需要找到一个空单元。线性探查法的问题是数组内相邻单元的数据元素会趋近聚类(将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类)。http://www.nowamagic.net/academy/detail/3008050(转)
平方探查法:等待补充...