hash表进阶


散列法的基本思想是把键分布在一个称为散列表的一维数组H[0.......m-1]中,我们可以对每个键计算某些被称为散列函数的预定义函数h的值,来完成这种分布。该 函数为每个键确定它在H中的下标

一般来说,hash函数的要求:

1)使得键在hash表里尽可能均匀的分布

2)hash函数必须容易计算


显然,如果选择的散列表长度m小于键的数量n,则会 碰撞。这是一种2个或多个键被hash到散列表的同一个单元格的情况。即使m相对于n足够大,这种情况仍会发生。

2种解决碰撞的方法:开散列(分离链)和闭散列(开放地址),以下分别介绍,并用开散列的方式模拟实现一个hash表。


----------------------------------------------------------------------------------------------------------------------------------------------------

1,开散列解决冲突(分离链)

这种方式将同一hash值的键用一个链表连接起来。如下图所示:



假设我们构造的hash函数使得ARE和SOON的hash值都是11(如上图),那么他们都存在值为11处的链表中。


hash表上的操作:查询,插入,删除

先介绍一个概念:负载因子,如果一个散列函数比较均匀的将n个键分布在散列表的m个单元格中,那么每个单元格对应的链表平均长度就是n/m (即平均每个hash值都有n/m元素冲突),我们称n/m为负载因子α。

查询:负载因子α的值(即平均每个链表的长度)决定了查询的效率

插入和删除:在链表里插入和删除。

我们希望负载因子α和1不要相差太大,如果太小将意味着非常多的空链表,因而没有好好地利用空间,如果太大将意味着平均每个链表长度很长,查询效率很低。


--------------------------------------------------------------------------------------------------------------------------------------------------


2,闭散列


在闭散列中,所有键都存储在散列表本身中,而没有使用链表。(当然,这意味这表的长度至少必须和键的数量一样大,而分离链的方式表的长度可以比键数量小)。

可以采用不同的策略来解决碰撞,最简单的一种称为 线性探查,它检查碰撞发生处后面的单元格,如果该单元格为空,新的键就放在那里,如果下一个单元格也被占用了,就继续检查下下一个单元格(检查到尾部的时候返回头部,把数组看做循环的),以此类推,直到找到一个可用单元格。

闭散列上的查找,插入,删除:

查找:对于键K,计算哈希值为h(K),如果单元格h(K)处为空,查找失败,否则查找下一个单元格,直到遇到匹配滴(查找成功)或遇到一个空单元格(查找失败)

插入:上面已描述

删除:跟查找和插入不一样!如果你简单的把一个键从散列表中删除,将会导致它后面的键可能查询不到(因为查到被删除的这个地方的时候查找就结束了),一种办法是 延迟删除,用一个标记来标记该位置曾被占用过,但现在实际已删除了,到下一次可以插入到它的时候再把原来的值覆盖。


散列表将满的时候闭散列容易产生 聚类的现象:一系列连续的单元格被占据(查找效率滴)。

改进散列函数,和二次散列都可以改进闭散列的性能。


--------------------------------------------------------------------------------------------------------------------------------------------------


3,模拟hash表的实现(分离链解决冲突)

实现一个hash表:

基本操作:查询,插入,删除

注意要用链表把元素拉起来,就需要一个节点Node类,把元素放在Node里:

复制代码
  
  
package Section7; public class HashNode { // hash链里面的结点 public int element; public HashNode next; public HashNode( int element) { this .element = element; next = null ; } }
复制代码


hash表里用一个数组存放每个链表的第一个节点,还要记录当前的节点数:

  
  
private HashNode[] hashtable; // 在hashtable数组里面存放的是第一个顶点,冲突顶点链接到后面 private int count; // 记录hash表中实际有的结点数目


完整的结构:

复制代码
  
  
public class HashTable { /** * @param args */ private HashNode[] hashtable; // 在hashtable数组里面存放的是第一个顶点,冲突顶点链接到后面 private int count; // 记录hash表中实际有的结点数目 public HashTable( int n){ // 哈希表的大小 hashtable = new HashNode[n]; count = 0 ; } public int hash( int x){ // hash函数 return x % 11 ; } public int size(){ return count; } public void insertHash( int x){ } public int deleteHash( int x){ } public int searchHash( int x){ } public static void main(String[] args){ }
复制代码


哈希表上的操作:查询,插入,删除,其实就是根据hash值在链表里进行链表的插入,删除,查询操作,都不难,就是删除还比较麻烦,情况比较多,注意一下:

查询:

复制代码
  
  
public int searchHash( int x){ // 在hash表中查找元素x的下标,若没有,返回-1 int hashValue = hash(x); if (hashtable[hashValue] == null ) // 如果链表为空 return - 1 ; else // 链表不空 { HashNode lastNode = hashtable[hashValue]; while (lastNode != null ) { if (lastNode.element == x) return hashValue; lastNode = lastNode.next; } } return - 1 ; }
复制代码


插入:

复制代码
  
  
public void insertHash( int x){ // 向hash表插入元素x int hashValue = x % 11 ; HashNode node = new HashNode(x); if (hashtable[hashValue] == null ) hashtable[hashValue] = node; else { HashNode lastNode = hashtable[hashValue]; while ( null != lastNode.next) lastNode = lastNode.next; lastNode.next = node; } count ++ ; }
复制代码



删除:

复制代码
  
  
public int deleteHash( int x){ // 在hash表中删除元素x,并返回x的下标,若x不存在返回-1 int hashValue = hash(x); if (hashtable[hashValue] == null ) return - 1 ; else // 至少一个元素 { if (hashtable[hashValue].next == null ) // 如果只有一个元素 { if (hashtable[hashValue].element == x) { hashtable[hashValue] = null ; count -- ; return hashValue; } } else // 链表里至少2个元素 { if (hashtable[hashValue].element == x) // 第一个元素就相等 { hashtable[hashValue] = hashtable[hashValue].next; count -- ; return hashValue; } else // 第一个元素不相等 { HashNode preNode = hashtable[hashValue],currentNode = preNode.next; while (currentNode != null ) { if (currentNode.element == x) { preNode.next = currentNode.next; count -- ; return hashValue; } preNode = currentNode; currentNode = currentNode.next; } } } } return - 1 ; }
复制代码



--------------------------------------------------------------------------------------------------------------------------------------------------



总结:

模拟hash表的实现


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值