标签:
一:散列函数
①:如果输入的关键字是整数:
比较合理的方法就是直接返回"key mod TableSize"。当然这需要仔细考虑过,通常保证表的大小是素数,否则比如如果表的大小是10而关键字都以0结尾= =
②:如果关键字是字符串:
通常会选择把ASCII码加起来。
二:解决冲突的方法
冲突就是值两个不同的值经过散列函数的计算后分到了同一个格子里
①:分离链接法:
就是把散列到同一个值的所有元素保留到一个链表中。
所以我们要创建一个HashTable的结构,里面有两个元素:TableSize,还有一个数组。TableSize是哈希表的大小,数组中存放的是一个个指针。每个指针指向一个链表。
1 typedef struct ListNode *Position; 2 typedef Position List; 3 4 struct ListNode{ 5 int Element; 6 Position Next; 7 8 }; 9 10 struct HashTb1{ 11 int TableSize; 12 List *TheLists; 13 };
然后其余都和链表操作差不多,什么查找啊删除啊插入啊,唯一不同的(要注意的)就是哈希表的初始化:
1 HashTable Initial(int TableSize){ 2 HashTable H; int i; 3 H = (HashTable)malloc(sizeof(struct HashTb1)); 4 H->TableSize = NextPrime(TableSize); //表的大小应该是素数 5 H->TheLists = (List*)malloc(sizeof(List)*H->TableSize); 6 for (i = 0; i < H->TableSize; i++){ 7 H->TheLists[i] = (Position)malloc(sizeof(struct ListNode)); 8 H->TheLists[i]->Next = NULL; 9 } 10 return H; 11 }
分析:
装载因子: 共存放n个元素,m个槽位,装载因子α=n/m:
对于一次不成功的查找(&插入,插入就是一次不成功的查找)来说:需要Θ(1+α).因为关键字都被等可能的散列到m个槽中任何一个,不成功时查找的元素有平均α个,加上计算h(k)的时间,所以为Θ(1+α).
对于一次成功的查找来说:所检查的元素就是x前面的元素多1,因为新的元素都是在表头插入的,所以出现在x之前的元素都是在x之后插入的。要确定期望的检查元素数目,我们对所有的加起来再取平均。 设指示器随机变量X_{i,j}=I{h(i)=h(j)}:就是指i,j这两个元素的哈希值相同,其概率是1/m,所以E(X_{i,j})=1/m。所以期望数目为E[(1/n) *sum(1+sum(X_{i,j}))]=1+α/2-α/(2n).具体推导见算法导论146页。 所以一次成功查找的时间也是Θ(1+α)。
所以分离链接法需要装载因子α≈1。
②开放定址法:
定义:如果我们计算出h(x)时,悲剧的发现冲突了,那就尝试h1(x),h2(x),其中hi(x)=(hash(x)+F(i))mod TableSize; F(0)=0,函数F是解决冲突解决方法。因为现在所有的数据都要填入表中,所以装载因子α应该小于1,一般来说是小于0.5,具体之后解释。
(1):线性探查法:
定义:F是i的线性函数。
缺点:容易形成一次群集。因为如果一个空槽前面有i个满的槽,该空槽下次被占用的概率是(i+1)/m.(因为如果计算出的哈希值是那i个槽,那肯定就放在该空槽里。)连续被占用的槽就会变得越来越长。
(2):平方探查法:
定义:解决冲突函数为二次函数
定理:如果表有一半是空的,且表的大小为素数,则肯定能插入一个新的元素。
证明:我们证明前一半的备选位置是互异的。(不是表的前一半)h(X)+i^2(mod TableSize)和h(X)+j^2(mod TableSize)是其中两个。其中0<i,j≤TableSize/2。
为了推出矛盾,我们假设这两个位置相同,
所以(i-j)(i+j)=0 (mod TableSize).
因为表的大小是素数,所以(i-j)或是(i+j)mod TableSize都不等于0,矛盾。
所以任何元素都有TableSize/2个可能被放到的位置。又因为现在表有一半是空的,那么肯定能找到一个位置插入元素。
声明:
1 typedef struct HashEntry Cell; 2 typedef struct HashTb1 *HashTable; 3 4 enum KindOfEntry{ Legitimate, Empty, Deleted };//枚举,记录这个格子是空的还是被删除了还是正常有值 5 struct HashEntry{ //Hash表的一个个格子 6 int Element; 7 enum KindOfEntry Info; 8 }; 9 10 struct HashTb1{ 11 int TableSize; 12 Cell *TheCells; 13 };
初始化:
1 HashTable Initial(int TableSize){ 2 HashTable H; 3 int i; 4 H = (HashTable)malloc(sizeof(struct HashTb1)); 5 H->TableSize = NextPrime(TableSize); 6 H->TheCells = (Cell*)malloc(sizeof(Cell)*H->TableSize); 7 for (i = 0; i < H->TableSize; i++){ 8 H->TheCells[i].Info = Empty;//注意是.Info 9 } 10 return H; 11 }
Find:
1 int Find(HashTable H,int X){ 2 int Position = Hash(X, H->TableSize); 3 int i=0; 4 while ((H->TheCells[Position].Info != Empty) && H->TheCells[Position].Element != X){ 5 Position += 2 * ++i - 1; 6 if (Position >= H->TableSize){ 7 Position -= H->TableSize; 8 } 9 } 10 return Position; 11 }
那个 Position+=2 * ++i - 1; 还是蛮有讲究的,因为是平方探测,所以F(i)=F(i-1)+2i-1,所以就可以理解啦。此举使得速度变快!
Insert:
1 void Insert(HashTable H, int X){ 2 int Pos; 3 Pos = Find(H, X); 4 if (H->TheCells[Pos].Info != Legitimate){ 5 H->TheCells[Pos].Info = Legitimate; 6 H->TheCells[Pos].Element = X; 7 8 } 9 }
(3):双散列:
取F(i)=i*hash2(X),这个第二个散列函数需要谨慎选择。
分析:
开放寻址法装载因子α=n/m,n≤m,也就是说α≤1.
对于均匀散列来说:
(1):对于一次不成功的查找,其期望的探查次数之多为1/(1-α)。
注意:上面之所以计算X≥i,是因为我只要知道前面i-1个都是被占用的,后面怎么样到底是有的还是没有的我就不用管了。
(2):插入一个元素需要做1/(1-α)次探查。
插入就是先做一次不成功的查找。
按照我的理解,这个α是随着插入元素数量的增多而改变的。恩><
③再散列:
在平方散列的开放定址法中,如果表中元素过满,那么运行时间就太长了。此时我们可以建立另外一个两倍大的表,把原表中的数据计算新散列值重新插入新表。当然再散列的时候可以用新的散列函数。
有三种做法: 表满一半就散列,插入失败再散列,表到达某个装载因子再散列。第三种书上说是最好的策略。
实现蛮简单的,就不写代码了。
疑惑:散列中老是提到表的大小要是素数,书上说是否则备选单元可能提前用完,就是说找不到可以放的位置了。不过我并不能理解,留作疑问。
标签:
原文地址:http://www.cnblogs.com/stezqy/p/4303853.html