查找算法总结(2)--哈希表

转载 2017年05月26日 20:17:19

一、简介

哈希表也叫散列表,是普通数组概念的推广。普通数组可以直接寻址,利用Loc(ai)=Loc(a1)+(i1)c,可以在O(1)时间内直接访问任意位置。函数Loc()是一种映射,将关键字映射到它的地址。同理,哈希表也是通过某个函数F,使得
存储位置=F(关键字),F称为散列函数或哈希函数。同时,不同的关键字通过映射后要尽量得到不同的值,这就要设计合适的哈希函数,另外一旦哈希函数失效,在同一个位置存储多个关键字,需要有解决冲突的方法。所以建立哈希表的关键在于两点:设计哈希函数和处理冲突的方法。
哈希表建立完成后,就是哈希查找了。哈希查找的过程和构建哈希表的过程一致。给定关键字,利用哈希函数计算其位置,如果有冲突的话,需要按照解决冲突的方法寻找关键字。时间复杂度在无冲突的哈希表中只需要O(1).
以下内容转自http://blog.csdn.net/xiaokang123456kao/article/details/54583062
一、为什么要用哈希表
树的操作通常需要O(N)的时间级,而哈希表中无论存有多少数据,它的插入和查找(有时包括删除)只需要接近常量级的时间,即O(1)的时间级。
但是哈希表也有一定的缺点:它是基于数组的,数组创建后难以扩展。而某些哈希表在基本填满时,性能下降明显,所以事先必须清楚哈希表中将要存储多少数据。而且目前没有一种简便的方法可以对哈希表进行有序(从大到小或者从小到大)的遍历,除非哈希表本身是有序的,但事实上这是违背哈希原则的。
综合以上:当不需要有序遍历数据,而且可以提前预测需要存储的数据项的数目,使用哈希表的结构是十分方便的。
二、哈希化
把巨大的整数(关键字)范围压缩到一个可接受的数组范围内,便于存储和查找。通常来说,我们要存储5000个数据,但数据的关键字范围可能是0-200000。我们不可能去开辟200000的数组去存储这5000个数据,这就需要一个函数把关键字和数组下标对应起来。这就是哈希函数。通常的做法是取余操作。i=N%size;i为下标,N为关键字,size为数组大小。不过通常来说,size设为要存储数据项数目的两倍。
如果哈希表存满时,需要扩展哈希表。我们需要新建一个更大的数组来存储数据,然后把原表中数据一一取出放入新表中。需要注意的是数据放入新表时需要重新用哈希函数计算哈希值,不能直接进行数组的复制,因为哈希函数的size已经变了。
通常而言我们把哈希数组的容量设为一个质数。首先来说假如关键字是随机分布的,那么无所谓一定要模质数。但在实际中往往关键字有某种规律,例如大量的等差数列,那么公差和模数不互质的时候发生碰撞的概率会变大,而用质数就可以很大程度上回避这个问题。对于除法哈希表h(k)=k mod m,注意二进制数对取余就是该二进制数最后r位数。这样一来,Hash函数就和键值(用二进制表示)的前几位数无关了,这样我们就没有完全用到键值的信息,这种选择m的方法是不好的。所以好的方法就是用质数来表示m,使得这些质数,不太接近2的幂或者10的幂。
三、解决冲突
首先一般哈希表是不允许重复的关键字,否则查找函数只能返回最先查到的关键字,无法找到所有的对应数据项。如果重写查找函数让它可以找到所有的对应数据项,这又会使得无论是否是重复关键字,查找操作都要搜索整个表,非常耗时。
存储过程中可能出现存储的数据项关键字不同,但计算出来的哈希值是相同的,这就是冲突。
通常采用以下两种方法来解决冲突。
1、开放地址法
直接在哈希表中找到一个空位,把冲突的数据项存进去。
2、链地址法
把哈希表中存储的数据格式设为链表,这样可以把冲突的数据放入对应位置的链表中即可。

二、开放地址法的Java实现

根据在查找下一个空位置时采用的方法,可以把开放地址法分为三种:线性探测、二次探测和再哈希法。

2.1 线性探测

/构造哈希表中的元素
public class DataItem {
    private int iData;               // 设为关键字  
    //--------------------------------------------------------------  
    public DataItem(int ii)          // 构造器  
       { iData = ii; }  
    //--------------------------------------------------------------  
    public int getKey()  //获取关键字  
       { return iData; }  
}
//实现线性探测法哈希表
public class HashTable1 {
    private DataItem[] hashArray;    // 数组形式
    private int arraySize;           //哈希表的大小
    private DataItem nonItem;        // 删除数据时,将被删除的数据设为nonItem
    //-------------------------------------------------------------
    public HashTable1(int size)       //构造器,指定哈希表的大小
       {
       arraySize = size;
       hashArray = new DataItem[arraySize];
       nonItem = new DataItem(-1);   // 把nonItem的关键字设为-1
       }
    //-------------------------------------------------------------
    public void displayTable()       //显示哈希表
       {
       System.out.print("Table: ");
       for(int j=0; j<arraySize; j++)
          {
          if(hashArray[j] != null)
             System.out.print(hashArray[j].getKey() + " ");
          else
             System.out.print("** ");  //该位置没有存数据
          }
       System.out.println("");
       }
    //-------------------------------------------------------------
    public int hashFunc(int key)
       {
       return key % arraySize;       // 哈希函数
       }
    //-------------------------------------------------------------
    public void insert(DataItem item) // 插入数据
    // 默认表未满,事实上哈希表是不允许存满的,哈希表的大小比实际存储的数据数要大。
       {
       int key = item.getKey();      // 获取数据项的关键字,用于计算哈希值
       int hashVal = hashFunc(key);  // 计算哈希值
                                     // 当前位置存有数据并且该数据未被删除
       while(hashArray[hashVal] != null &&
                       hashArray[hashVal].getKey() != -1)
          {
          ++hashVal;                 // 查找下一个位置
          hashVal %= arraySize;      // 到达表的末尾时,hashVal值变成1,。构成循环,从而可以查找整个表
          }
       hashArray[hashVal] = item;    // 找到位置
       }  // end insert()
    //-------------------------------------------------------------
    public DataItem delete(int key)  // 根据关键字删除数据
       {
       int hashVal = hashFunc(key);  // 根据关键字计算哈希值

       while(hashArray[hashVal] != null)  // 该位置存有数据
          {                               // 两者的关键字是否相同
          if(hashArray[hashVal].getKey() == key)
             {
             DataItem temp = hashArray[hashVal]; // 保存删除的数据项,用于返回
             hashArray[hashVal] = nonItem;       // 删除
             return temp;                        // 返回删除的数据项
             }
          ++hashVal;                 // 关键字不相同,继续查找下一个
          hashVal %= arraySize;      //循环
          }
       return null;                  // 未找到
       }  // end delete()
    //-------------------------------------------------------------
    public DataItem find(int key)    // 表中是否存在该关键字的数据项
       {
       int hashVal = hashFunc(key);  

       while(hashArray[hashVal] != null)  
          {                               
          if(hashArray[hashVal].getKey() == key)
             return hashArray[hashVal];   
          ++hashVal;              
          hashVal %= arraySize;      
          }
       return null;                
       }
    //-------------------------------------------------------------

}

2.2 二次探测法

线性探测法会发生集聚现象,即冲突数据项会集聚在一起,原因是查找空数据项是一步一步移动的。
二次探测法是为了防止集聚产生的一种尝试方法,思想是探测间隔较远的单元,而不是临近的单元。具体方法是把步长设为探测次数的平方,比如第1次探测步长为1,第2次为4,第3次为9以此类推。
但是二次探测法会产生二次集聚。通常不采用该方法,因为有更好的解决方案。

2.3 再哈希法

方法是对冲突的关键字用另一个哈希函数计算其值,把结果作为搜索时的步长。这就使得不同的关键字步长不同,避免了集聚现象。
第二个哈希函数必须具备以下条件:
(1)与第一个哈希函数不同
(2)不能得出结果为0,否则步长为0.
通常第二个哈希函数采用如下函数:
step=constant-(key%contant)
constant是一个质数且小于数组容量,key是关键字。step范围在1-constant之间。
再哈希法要求表的容量是一个质数,这是为了使查找操作可以遍历整个表。否则假设表的容量为15,不是一个质数。而查找初始位置为4,查找步长为5,那么每次查找都是固定的三个数,即下标为9,14,4对应的数据。设为质数可以避免这种情况。

//实现再哈希法哈希表,包括哈希查找
public class HashTable2 {
    private DataItem[] hashArray; 
    private int arraySize;
    private DataItem nonItem;        
    //-------------------------------------------------------------
    public HashTable2(int size)               // 构造器
       {
       arraySize = size;
       hashArray = new DataItem[arraySize];
       nonItem = new DataItem(-1);
       }
    //-------------------------------------------------------------
    public void displayTable()
       {
       System.out.print("Table: ");
       for(int j=0; j<arraySize; j++)
          {
          if(hashArray[j] != null)
             System.out.print(hashArray[j].getKey()+ " ");
          else
             System.out.print("** ");
          }
       System.out.println("");
       }
    //-------------------------------------------------------------
    public int hashFunc1(int key)
       {
       return key % arraySize;
       }
    //-------------------------------------------------------------
    public int hashFunc2(int key) //再哈希
       {
       return 5 - key % 5;
       }
    //-------------------------------------------------------------

    public void insert(int key, DataItem item)
    // 假设表未满
       {
       int hashVal = hashFunc1(key);  // 计算哈希值
       int stepSize = hashFunc2(key); // 计算步长

       while(hashArray[hashVal] != null &&
                       hashArray[hashVal].getKey() != -1)//非空且数据未删除
          {
          hashVal += stepSize;        // 加步长
          hashVal %= arraySize;       // 循环到表头
          }
       hashArray[hashVal] = item;     // 插入
       }  // end insert()
    //-------------------------------------------------------------
    public DataItem delete(int key)   // 删除
       {
       int hashVal = hashFunc1(key);      //计算哈希值
       int stepSize = hashFunc2(key);     // 计算步长

       while(hashArray[hashVal] != null)  // 非空
          {                               
          if(hashArray[hashVal].getKey() == key)//找到
             {
             DataItem temp = hashArray[hashVal]; 
             hashArray[hashVal] = nonItem;       
             return temp;                       
             }
          hashVal += stepSize;           
          hashVal %= arraySize;           
          }
       return null;                   // 无法找到
       }  // end delete()
    //-------------------------------------------------------------
    public DataItem find(int key)     // 查找
    // 假设表未满
       {
       int hashVal = hashFunc1(key);       
       int stepSize = hashFunc2(key);     

       while(hashArray[hashVal] != null)  // 非空
          {                               
          if(hashArray[hashVal].getKey() == key)
             return hashArray[hashVal];   // 找到返回
          hashVal += stepSize;            // 加步长
          hashVal %= arraySize;          
          }
       return null;                   // can't find item
       }
}

三、如何设计哈希函数

1、不使用无用数据项
关键字的选取时,要提出原始数据中的无用数据项,例如起始位、校验位、结束位等,因为这些数据位没有携带信息。
2、使用所有的有用数据位
所有的有用数据位在哈希函数中都应当有体现。不要使用前四位或者后五位等其他方法。
3、使用质数作为取模运算的基数。
若关键字完全随机分布,质数和非质数的表现差不多。但是当关键字不是随机分布时,就应该使用质数作为哈希表的大小。使用质数可以是关键字较为平均的映射到哈希表的各个位置。

四、开放地址法和链地址法的比较

开放地址法在表快满时,性能有明显下降,且对哈希表进行扩展时操作复杂。链地址法需要设计链表类,但是不会随着数据项的增多导致性能快速下降,而且可以动态扩展哈希表。

数据结构--七大查找算法总结

阅读目录 1. 顺序查找2. 二分查找3. 插值查找4. 斐波那契查找5. 树表查找6. 分块查找7. 哈希查找   查找是在大量的信息中寻找一个特定的信息元素,在计算机应用中,查找是常用的基本...
  • sayhello_world
  • sayhello_world
  • 2017年08月15日 21:06
  • 7116

查找算法总结

==================== 顺序查找算法 ==================== 1. 算法描述   顺序比较即可。 2. 平均查找长度   (n+1)/2, 其中n为表长...
  • fstar007
  • fstar007
  • 2012年02月03日 11:05
  • 25686

查找算法总结

静态查找结构主要有两种:顺序查找、折半查找   一、顺序查找:这个就不用说了,一个一个的差吧,很差劲的算法了,时间复杂度是O(n)      public int shunXuSearch( i...
  • ranjiewen
  • ranjiewen
  • 2016年09月20日 14:05
  • 1950

五种查找算法总结

五种查找算法总结    一、顺序查找   条件:无序或有序队列。   原理:按顺序比较每个元素,直到找到关键字为止。   时间复杂度:O(n) 二、二分查找(折半查找)   条件...
  • m372897500
  • m372897500
  • 2016年05月23日 01:12
  • 2574

C语言实现哈希表查找算法

哈希表(散列表)是直接通过关键字key得到要查找的记录的内存存储位置。 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。 采用...
  • HEYIAMCOMING
  • HEYIAMCOMING
  • 2017年08月18日 15:39
  • 600

各种排序、查找算法总结

1.直接插入排序 第i(i>1)个数,依次插入到前i-1个数组成的有序序列(上升序列,下降序列)中。 2.二分插入排序(直接插入排序的改进算法) 第i(i>1)个数,依次插入到前i-1个数组...
  • zhibuguonicuo
  • zhibuguonicuo
  • 2016年10月14日 20:18
  • 461

常用查找算法总结

查找是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)查找表按照操作方式分为静态查找表和动态查找表。静态查找表:只作查找操作的查找表。它的主要操作是: (1)查询某个“特定...
  • hejun_haitao
  • hejun_haitao
  • 2016年09月25日 17:08
  • 519

查找算法总结

主要参考七大查找算法 无序查找:被查找数列有序无序均可; 有序查找:被查找数列必须为有序数列。 顺序查找 就是平时常用的暴力搜索,属于无序查找算法。从数据结构线形表的一端开始,顺序...
  • Cheese_pop
  • Cheese_pop
  • 2018年03月02日 20:57
  • 63

有序查找的三种算法

#include int Binary_Search(int *a,int n, int key) { int low,high,mid; low = 0; high = n; while...
  • chencangui
  • chencangui
  • 2015年03月27日 21:50
  • 1231

十.用C语言实现查找算法 (1)顺序查找;(2)二分查找(折半查找);(3)二叉排序树;(4)哈希查找

程序名称:Search.cpp // 程序功能:采用结构化方法设计程序,实现多种查找算法。 // 程序作者:*** // 最后修改日期:2011-3-3 #include"iostream" #inc...
  • u011676589
  • u011676589
  • 2013年08月16日 07:59
  • 3308
收藏助手
不良信息举报
您举报文章:查找算法总结(2)--哈希表
举报原因:
原因补充:

(最多只允许输入30个字)