博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

字符串哈希函数

Posted on 2023-03-20 14:11  linFen  阅读(39)  评论(0编辑  收藏  举报

1、概念

哈希之所以广泛存在,是因为它能在绝大多数情况下可以在O(1)的时间复杂度中完成元素的查找。它的核心是数组,如果输入是一个自然数,那么当然可以在常数时间内搜索到自然数所对应的数组元素了。但在工程实践中,要查找的关键字往往都不是自然数,即使是自然数也有可能是很大的值。因此,只要我们提前把关键字转换为在固定较小范围内的自然数,就可以实现常数时间的查找。那么问题来了,如何实现该转换关系呢?这就是哈希函数所要完成的工作。

哈希函数:又称散列函数,是把一段有限二进制串(字符串,整数等)转换为自然数的一种函数。
哈希值:哈希函数输出的最终结果。
字符串哈希函数:输入是字符串的哈希函数。


既然是函数,就有可能出现多对一的情况(多个输入对应同一个哈希值),这种情况称为冲突。没有冲突的哈希函数称为完全哈希函数,但这种函数只在理论分析中出现。为了保证每一个元素对应不同的存储地址,可使用以下两类方法:
链接法:数组元素存储指向链表的指针,链表的每个元素都可以存储一个输入的元素。
开放地址法:所有的元素都存放在散列表中,它不用指针,是计算出要存取的槽序列。

使用哈希函数得到哈希值只是Hash算法的第一步,此外一般还需要进行高位运算和取模运算。


2、常见方法

评价hash函数性能的一个重要指标就是冲突,在相关资源允许的条件下冲突越少hash函数的性能越好。显然,对于字符串哈希函数,尽可能使每个字符都影响哈希值可以减少冲突。目前常见的字符串hash算法有BKDRHash,APHash,DJBHash,

JSHash,RSHash,SDBMHash,PJWHash,ELFHash等等。

其中数据1为100000个字母和数字组成的随机串哈希冲突个数。数据2为100000个有意义的英文句子哈希冲突个数。数据3为数据1的哈希值与 1000003(大素数)求模后存储到线性表中冲突的个数。数据4为数据1的哈希值与10000019(更大素数)求模后存储到线性表中冲突的个数。

经过比较,得出以上平均得分。平均数为平方平均数。可以发现,BKDRHash无论是在实际效果还是编码实现中,效果都是最突出的。APHash也是较为优秀的算法。DJBHash,JSHash,RSHash与SDBMHash各有千秋。PJWHash与ELFHash效果最差,但得分相似,其算法本质是相似的。


3、实现原理和常用算法

所有的字符串哈希算法都是基于对字符编码的迭代运算,只是运算规则不同而已。

1)BKDRHash算法

BKDRHash是Kernighan和Dennis在《The C programming language》中提出的。

    // BKDR Hash Function
    unsigned int BKDRHash(char *str)
    {
        unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
        unsigned int hash = 0;
     
        while (*str)
        {
            hash = hash * seed + (*str++);
        }
     
        return (hash & 0x7FFFFFFF);
    }

 

2)APHash算法

Arash Partow提出了这个算法,声称具有很好地分布性。

    // AP Hash Function
    unsigned int APHash(char *str)
    {
        unsigned int hash = 0;
        int i;
     
        for (i=0; *str; i++)
        {
            if ((i & 1) == 0)
            {
                hash ^= ((hash << 7) ^ (*str++) ^ (hash >> 3));
            }
            else
            {
                hash ^= (~((hash << 11) ^ (*str++) ^ (hash >> 5)));
            }
        }
     
        return (hash & 0x7FFFFFFF);
    }

3)DJBHash算法

Daniel J. Bernstein在comp.lang.c邮件列表中发表的,是距今为止比较高效的哈希函数之一。

    // DJB Hash Function
    unsigned int DJBHash(char *str)
    {
        unsigned int hash = 5381;
     
        while (*str)
        {
            hash += (hash << 5) + (*str++);
        }
     
        return (hash & 0x7FFFFFFF);
    }

4)JSHash算法

Justin Sobel提出的基于位的函数函数。

   // JS Hash Function
    unsigned int JSHash(char *str)
    {
        unsigned int hash = 1315423911;
     
        while (*str)
        {
            hash ^= ((hash << 5) + (*str++) + (hash >> 2));
        }
     
        return (hash & 0x7FFFFFFF);
    }

5)RSHash算法

其作者是Robert Sedgwicks。实现如下:

    // RS Hash Function
    unsigned int RSHash(char *str)
    {
        unsigned int b = 378551;
        unsigned int a = 63689;
        unsigned int hash = 0;
     
        while (*str)
        {
            hash = hash * a + (*str++);
            a *= b;
        }
     
        return (hash & 0x7FFFFFFF);
    }

6)SDBMHash算法

SDBM项目使用的哈希函数,声称对所有的数据集有很好地分布性。

    unsigned int SDBMHash(char *str)
    {
        unsigned int hash = 0;
     
        while (*str)
        {
            // equivalent to: hash = 65599*hash + (*str++);
            hash = (*str++) + (hash << 6) + (hash << 16) - hash;
        }
     
        return (hash & 0x7FFFFFFF);
    }

7)PJWHash算法

Peter J. Weinberger在其编译器著作中提出的。

    // P. J. Weinberger Hash Function
    unsigned int PJWHash(char *str)
    {
        unsigned int BitsInUnignedInt = (unsigned int)(sizeof(unsigned int) * 8);
        unsigned int ThreeQuarters    = (unsigned int)((BitsInUnignedInt  * 3) / 4);
        unsigned int OneEighth        = (unsigned int)(BitsInUnignedInt / 8);
        unsigned int HighBits         = (unsigned int)(0xFFFFFFFF) << (BitsInUnignedInt - OneEighth);
        unsigned int hash             = 0;
        unsigned int test             = 0;
     
        while (*str)
        {
            hash = (hash << OneEighth) + (*str++);
            if ((test = hash & HighBits) != 0)
            {
                hash = ((hash ^ (test >> ThreeQuarters)) & (~HighBits));
            }
        }
     
        return (hash & 0x7FFFFFFF);
    }

8)ELFHash算法

Unix系统上面广泛使用的哈希函数。

    // ELF Hash Function
    unsigned int ELFHash(char *str)
    {
        unsigned int hash = 0;
        unsigned int x    = 0;
     
        while (*str)
        {
            hash = (hash << 4) + (*str++);
            if ((x = hash & 0xF0000000L) != 0)
            {
                hash ^= (x >> 24);
                hash &= ~x;
            }
        }
     
        return (hash & 0x7FFFFFFF);
    }

9)DEKHash算法

Donald E. Knuth在《计算机程序设计的艺术》中提出的哈希函数。

public static int dekhash(String str) {
      int hash = str.length();
 
      for (int i = 0; i < str.length(); i++) {
         hash = (hash << 5) ^ (hash >> 27) ^ (int)str.charAt(i);
      }
 
      return hash & 0x7FFFFFFF;
   }

10)BPHash算法

public static int bphash(String str) {
      int hash = str.length();
 
      for (int i = 0; i < str.length(); i++) {
         hash = (hash << 7) ^ (int)str.charAt(i);
      }
 
      return hash & 0x7FFFFFFF;
   }   

11)FNVHash算法

public static int fnvhash(String str) {
      int fnvprime = 0x811C9DC5;
      int hash = 0;
 
      for (int i = 0; i < str.length(); i++) {
         hash *= fnvprime;
         hash ^= (int)str.charAt(i);
      }
 
      return hash & 0x7FFFFFFF;
   }

12)Java String Hashcode算法

这是Java的字符串类的Hash算法,简单实用高效。

public static int javahash(String str) {
      int hash = 0;
 
      for (int i = 0; i < str.length(); i++) {
         hash = hash * 31 + (int)str.charAt(i);
      }
 
      return hash & 0x7FFFFFFF;
   }