hash函数

     
由于本人水平有限,文章中难免有错误,望请指出,万分感谢。


     思想:利用映射技术,将目标数据映射到某一个地址,且该地址可以进行直接定位。
     映射技术实现的方法:
          除法散列:h(k) = k mod m ;
               m一般为与2的幂不太接近的质数。原因:当m以2的幂结尾时,余数为k除以2的幂的位数,这样产生冲突的概率加大了
          乘法散列:  h(k)  = m (kA mod 1)
               其中m一般为2的幂,A一般在0-1之间,不进行具体要求,Knuth认为A=(根号5-1)/2是一个较好的数。
          还有其他好多方法,有兴趣的可以自己搜索一下。
     使用方面:一般用于数据的查找,即在理想情况下,消耗O(1)时间进行数据的查找。
                     但是要求表的大小小于内存大小。
     出现问题:在存储数据的过程中,当两个数据冲突时候,即:两个不同数据映射到同一个地址,此时产生了冲突,我们称之为碰撞。
     碰撞解决方法:
            1:开放地址法
                   即线性探测法,当产生冲突时候,进行顺序检测,是否有空间存放数据。
            2:再hash法
                   当产生冲突时候,利用一个新的hash函数再次hash;
           3:链地址法
                   当产生冲突时候在冲突位置后接一个链表,存储冲突数据。
           4:建立公共溢出区
                  建立溢出区,产生冲突就把冲突元素放到溢出区中。


     一般情况下是将字符串映射为一个整数值,作为数组下标。
     介绍一下MPQ中的Hash算法(参看自此文 http://sfsrealm.hopto.org/inside_mopaq/chapter2.htm

      函数一、以下的函数生成一个长度为0x500(合10进制数:1280)的cryptTable[0x500]

void prepareCryptTable()
{
    unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i;
 
    for( index1 = 0; index1 < 0x100; index1++ )
    {
        for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 )
        {
            unsigned long temp1, temp2;
 
            seed = (seed * 125 + 3) % 0x2AAAAB;
            temp1 = (seed & 0xFFFF) << 0x10;
 
            seed = (seed * 125 + 3) % 0x2AAAAB;
            temp2 = (seed & 0xFFFF);
 
            cryptTable[index2] = ( temp1 | temp2 );
       }
   }

函数二、以下函数计算lpszFileName 字符串的hash值,其中dwHashType 为hash的类型,在下面的函数三、GetHashTablePos函数中调用此函数二,其可以取的值为0、1、2;该函数返回lpszFileName 字符串的hash值: 

unsigned long HashString( char *lpszFileName, unsigned long dwHashType )
{
      unsigned char *key  = (unsigned char *)lpszFileName;
      unsigned long seed1 = 0x7FED7FED;
      unsigned long seed2 = 0xEEEEEEEE;
      int ch;
 
      while( *key != 0 )
      {
          ch = toupper(*key++);
 
          seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
          seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;
      }
      return seed1;
}


    Blizzard的这个算法是非常高效的,被称为"One-Way Hash"( A one-way hash is a an algorithm that is constructed in such a way that deriving the original string (set of strings, actually) is virtually impossible)。举个例子,字符串"unitneutralacritter.grp"通过这个算法得到的结果是0xA26067F3。

  是不是把第一个算法改进一下,改成逐个比较字符串的Hash值就可以了呢,答案是,远远不够,要想得到最快的算法,就不能进行逐个的比较,通常是构造一个哈希表(Hash Table)来解决问题,哈希表是一个大数组,这个数组的容量根据程序的要求来定义,例如1024,每一个Hash值通过取模运算 (mod) 对应到数组中的一个位置,这样,只要比较这个字符串的哈希值对应的位置有没有被占用,就可以得到最后的结果了,想想这是什么速度?是的,是最快的O(1),现在仔细看看这个算法吧:

typedef struct
{
    int nHashA;
    int nHashB;
    char bExists;
   ......
} SOMESTRUCTRUE;
一种可能的结构体定义?

函数三、下述函数为在Hash表中查找是否存在目标字符串,有则返回要查找字符串的Hash值,无则,return -1.

int GetHashTablePos( har *lpszString, SOMESTRUCTURE *lpTable )
//lpszString要在Hash表中查找的字符串,lpTable为存储字符串Hash值的Hash表。
{
    int nHash = HashString(lpszString);  //调用上述函数二,返回要查找字符串lpszString的Hash值。
    int nHashPos = nHash % nTableSize;
 
    if ( lpTable[nHashPos].bExists  &&  !strcmp( lpTable[nHashPos].pString, lpszString ) )
    {  //如果找到的Hash值在表中存在,且要查找的字符串与表中对应位置的字符串相同,
        return nHashPos;    //则返回上述调用函数二后,找到的Hash值
    }
    else
    {
        return -1; 
    }
}

 
    看到此,我想大家都在想一个很严重的问题:“如果两个字符串在哈希表中对应的位置相同怎么办?”,毕竟一个数组容量是有限的,这种可能性很大。解决该问题的方法很多,我首先想到的就是用“链表”,感谢大学里学的数据结构教会了这个百试百灵的法宝,我遇到的很多算法都可以转化成链表来解决,只要在哈希表的每个入口挂一个链表,保存所有对应的字符串就OK了。事情到此似乎有了完美的结局,如果是把问题独自交给我解决,此时我可能就要开始定义数据结构然后写代码了。

    然而Blizzard的程序员使用的方法则是更精妙的方法。基本原理就是:他们在哈希表中不是用一个哈希值而是用三个哈希值来校验字符串。

 

    MPQ使用文件名哈希表来跟踪内部的所有文件。但是这个表的格式与正常的哈希表有一些不同。首先,它没有使用哈希作为下标,把实际的文件名存储在表中用于验证,实际上它根本就没有存储文件名。而是使用了3种不同的哈希:一个用于哈希表的下标,两个用于验证。这两个验证哈希替代了实际文件名。
    当然了,这样仍然会出现2个不同的文件名哈希到3个同样的哈希。但是这种情况发生的概率平均是:1:18889465931478580854784,这个概率对于任何人来说应该都是足够小的。现在再回到数据结构上,Blizzard使用的哈希表没有使用链表,而采用"顺延"的方式来解决问题,看看这个算法:

函数四、lpszString 为要在hash表中查找的字符串;lpTable 为存储字符串hash值的hash表;nTableSize 为hash表的长度: 

int GetHashTablePos( char *lpszString, MPQHASHTABLE *lpTable, int nTableSize )
{
    const int  HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
 
    int  nHash = HashString( lpszString, HASH_OFFSET );
    int  nHashA = HashString( lpszString, HASH_A );
    int  nHashB = HashString( lpszString, HASH_B );
    int  nHashStart = nHash % nTableSize;
    int  nHashPos = nHashStart;
 
    while ( lpTable[nHashPos].bExists )
   {
     /*如果仅仅是判断在该表中时候存在这个字符串,就比较这两个hash值就可以了,不用对
     *结构体中的字符串进行比较。这样会加快运行的速度?减少hash表占用的空间?这种
      *方法一般应用在什么场合?*/
        if (   lpTable[nHashPos].nHashA == nHashA
        &&  lpTable[nHashPos].nHashB == nHashB )
       {
            return nHashPos;
       }
       else
       {
            nHashPos = (nHashPos + 1) % nTableSize;
       }
 
        if (nHashPos == nHashStart)
              break;
    }
     return -1;
}

上述程序解释:

1.计算出字符串的三个哈希值(一个用来确定位置,另外两个用来校验)
2. 察看哈希表中的这个位置
3. 哈希表中这个位置为空吗?如果为空,则肯定该字符串不存在,返回-1。
4. 如果存在,则检查其他两个哈希值是否也匹配,如果匹配,则表示找到了该字符串,返回其Hash值。
5. 移到下一个位置,如果已经移到了表的末尾,则反绕到表的开始位置起继续查询 
6. 看看是不是又回到了原来的位置,如果是,则返回没找到
7. 回到3

其中MPQ方法参考:http://blog.csdn.net/v_july_v/article/details/6256463
                              http://sfsrealm.hopto.org/inside_mopaq/chapter2.htm

//QMPHash.h文件
#ifndef _QMPHASH_H__
#define _QMPHASH_H__
#include <string.h>


/*
使用方法:
  1.包含头文件 #include "QMPHash.h"
  2.包含命名空间。简单一点:using namespace QMP_HASH;
  3.定义数组(最好是静态或者全局数据,局部数据如果太大编译器会报错)。
    MPQHASHTABLE ...[...];
  4.调用InitQMPHash函数
  5.调用addItem_QMP向hash表添加数据
  6.调用isExist检查数据是够存在
*/

namespace QMP_HASH {

                

                 struct MPQHASHTABLE
                {
                                 bool bExists ;
                                 unsigned long nHashA;
                                 unsigned long nHashB;
                };

                 void InitQMPHash ( MPQHASHTABLE * lpTable ,const int nTableSize);
                

                 bool addItem_QMP ( const char * lpszString, MPQHASHTABLE *lpTable , const int nTableSize);
                

                 //crytTable[]里面保存的是HashString函数里面将会用到的一些数据
                 void prepareCryptTable (); 
                

                 //用于计算hash值
                 unsigned long HashString( const char * lpszFileName, unsigned long dwHashType  ); 
                

                 int GetHashTablePos ( const char * lpszString, MPQHASHTABLE *lpTable , const int nTableSize);
                
                 bool isExistQMP ( const char * lpszString, MPQHASHTABLE *lpTable , const int nTableSize);

}

#endif

//QMPHash.cpp文件
#include "QMPHash.h"


namespace QMP_HASH {
                 unsigned long cryptTable[0x500];
                
                 void InitQMPHash ( MPQHASHTABLE * lpTable ,const int nTableSize)
                {
                                 prepareCryptTable ();
                                 memset (lpTable , sizeof( MPQHASHTABLE )*nTableSize ,0);
                }

                 bool addItem_QMP ( const char * lpszString, MPQHASHTABLE *lpTable , const int nTableSize)
                {
                                 if ( isExistQMP ( lpszString, lpTable ,nTableSize ) ){
                                                 return true ;
                                }

                                 const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
                                 unsigned long nHash = HashString (lpszString , HASH_OFFSET);
                                 unsigned long nHashA = HashString (lpszString , HASH_A);
                                 unsigned long nHashB = HashString (lpszString , HASH_B);
                                 unsigned long nHashStart = nHash % nTableSize ;
                                 unsigned long nHashPos = nHashStart ;
                                
                                
                                 while (lpTable [ nHashPos]. bExists )
                                {              
                                                 nHashPos = (nHashPos + 1) % nTableSize;
                                                 if (nHashPos == nHashStart)
                                                                 break ;
                                }
                                 if ( !lpTable [ nHashPos]. bExists ){
                                                 lpTable [nHashPos ]. nHashA= nHashA ;
                                                 lpTable [nHashPos ]. nHashB = nHashB ;
                                                 lpTable [nHashPos ]. bExists = true ;
                                                 return true ;
                                }
                                 return false ;
                }



                 //crytTable[]里面保存的是HashString函数里面将会用到的一些数据
                 void prepareCryptTable () 
                {  
                                 unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i; 

                                 for ( index1 = 0; index1 < 0x100; index1 ++ ) 
                                {  
                                                 for ( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 ) 
                                                {  
                                                                 unsigned long temp1, temp2 ; 

                                                                 seed = (seed * 125 + 3) % 0x2AAAAB; 
                                                                 temp1 = (seed & 0xFFFF) << 0x10; 

                                                                 seed = (seed * 125 + 3) % 0x2AAAAB; 
                                                                 temp2 = (seed & 0xFFFF); 

                                                                 cryptTable [index2 ] = ( temp1 | temp2 );  
                                                }  
                                }  
                }

                 //用于计算hash值
                 unsigned long HashString( const char * lpszFileName, unsigned long dwHashType  ) 
                {  
                                 unsigned char * key  = ( unsigned char *) lpszFileName; 
                                 unsigned long seed1 = 0x7FED7FED; 
                                 unsigned long seed2 = 0xEEEEEEEE; 
                                 int ch ; 

                                 while ( *key != 0 ) 
                                {  
                                                 //ch = toupper(*key++);  不区分大小写
                                                 ch = *key ++;

                                                 seed1 = cryptTable [( dwHashType << 8) + ch ] ^ (seed1 + seed2); 
                                                 seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;  
                                } 
                                 return seed1 ;  
                } 

                 int GetHashTablePos ( const char * lpszString, MPQHASHTABLE *lpTable , const int nTableSize)
                {
                                 const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
                                 unsigned long nHash = HashString (lpszString , HASH_OFFSET);
                                 unsigned long nHashA = HashString (lpszString , HASH_A);
                                 unsigned long nHashB = HashString (lpszString , HASH_B);
                                 unsigned long nHashStart = nHash % nTableSize ;
                                 unsigned long nHashPos = nHashStart ;

                                 while (lpTable [ nHashPos]. bExists )
                                {
                                                 if (lpTable [ nHashPos]. nHashA == nHashA && lpTable[ nHashPos ].nHashB == nHashB )
                                                                 return nHashPos ;
                                                 else
                                                                 nHashPos = (nHashPos + 1) % nTableSize;

                                                 if (nHashPos == nHashStart)
                                                                 break ;
                                }

                                 return -1; //Error value
                }

                 bool isExistQMP ( const char * lpszString, MPQHASHTABLE *lpTable , const int nTableSize)
                {
                                 int pos = GetHashTablePos( lpszString ,lpTable , nTableSize);
                                 return pos !=-1 ? true: false;
                }
}


测试代码:

//主函数测试代码
#include <iostream>
#include <string>
#include "QMPHash.h"
using namespace std;
using namespace QMP_HASH;

/*
使用方法:
  1.包含头文件 #include "QMPHash.h"
  2.包含命名空间。简单一点:using namespace QMP_HASH;
  3.定义数组(最好是静态或者全局数据,定义局部数据如果太大编译器会报错)。
    MPQHASHTABLE ...[...];
  4.调用InitQMPHash函数
  5.调用addItem_QMP向hash表添加数据
  6.调用isExist检查数据是够存在
*/
const int tableSize = 1024;
MPQHASHTABLE lpTable [ tableSize];

int main ()
{
                 InitQMPHash (lpTable , tableSize);
                 string str ;
                 cin >>str ;
                 while ( str != "a" ){
                                 addItem_QMP (str . c_str(), lpTable ,tableSize );
                                 cout <<isExistQMP ( str. c_str(), lpTable ,tableSize )<< " "
                                                << GetHashTablePos (str . c_str(), lpTable ,tableSize )<< " "
                                                << lpTable [GetHashTablePos ( str. c_str(), lpTable ,tableSize )]. nHashA<< " "
                                                << lpTable [GetHashTablePos ( str. c_str(), lpTable ,tableSize )]. nHashB<< endl ;
                                 cin >>str ;
                }
                 return 0;
}








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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值