暴雪公司有个经典的字符串的hash公式
先提一个简单的问题,假如有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做?
有一个方法最简单,老老实实从头查到尾,一个一个比较,直到找到为止,我想只要学过程序设计的人都能把这样一个程序作出来,但要是有程序员把这样的程序交给用户,我只能用无语来评价,或许它真的能工作,但也只能如此了。
最合适的算法自然是使用HashTable(哈希表),先介绍介绍其中的基本知识,所谓Hash,一般是一个整数,通过某种算法,可以把一个字符串"压缩" 成一个整数,这个数称为Hash,当然,无论如何,一个32位整数是无法对应回一个字符串的,但在程序中,两个字符串计算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法

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

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

 int GetHashTablePos(char *lpszString, SOMESTRUCTURE *lpTable, int nTableSize)
{
int nHash = HashString(lpszString), nHashPos = nHash % nTableSize;
if (lpTable[nHashPos].bExists && !strcmp(lpTable[nHashPos].pString, lpszString))
return nHashPos;
else
return -; //Error value
}

看到此,我想大家都在想一个很严重的问题:"假如两个字符串在哈希表中对应的位置相同怎么办?",究竟一个数组容量是有限的,这种可能性很大。解决该问题的方法很多,我首先想到的就是用"链表",感谢大学里学的数据结构教会了这个百试百灵的法宝,我碰到的很多算法都可以转化成链表来解决,只要在哈希表的每个入口挂一个链表,保存所有对应的字符串就OK了。
事情到此似乎有了完美的结局,假如是把问题独自交给我解决,此时我可能就要开始定义数据结构然后写代码了。然而Blizzard的程序员使用的方法则是更精妙的方法。基本原理就是:他们在哈希表中不是用一个哈希值而是用三个哈希值来校验字符串。
中国有句古话"再一再二不能再三再四",看来Blizzard也深得此话的精髓,假如说两个不同的字符串经过一个哈希算法得到的入口点一致有可能,但用三个不同的哈希算法算出的入口点都一致,那几乎可以肯定是不可能的事了,这个几率是1:18889465931478580854784,大概是10的 22.3次方分之一,对一个游戏程序来说足够安全了。
现在再回到数据结构上,Blizzard使用的哈希表没有使用链表,而采用"顺延"的方式来解决问题,看看这个算法:

 int GetHashTablePos(char *lpszString, MPQHASHTABLE *lpTable, int nTableSize)
{
const int HASH_OFFSET = , HASH_A = , HASH_B = ;
int nHash = HashString(lpszString, HASH_OFFSET);
int nHashA = HashString(lpszString, HASH_A);
int nHashB = HashString(lpszString, HASH_B);
int nHashStart = nHash % nTableSize, nHashPos = nHashStart;
while (lpTable[nHashPos].bExists)
{
if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB)
return nHashPos;
else
nHashPos = (nHashPos + ) % nTableSize;
if (nHashPos == nHashStart)
break;
}
return -; //Error value
}

1. 计算出字符串的三个哈希值(一个用来确定位置,另外两个用来校验)
2. 察看哈希表中的这个位置
3. 哈希表中这个位置为空吗?假如为空,则肯定该字符串不存在,返回
4. 假如存在,则检查其他两个哈希值是否也匹配,假如匹配,则表示找到了该字符串,返回
5. 移到下一个位置,假如已经越界,则表示没有找到,返回
6. 看看是不是又回到了原来的位置,假如是,则返回没找到
7. 回到3
怎么样,很简单的算法吧,但确实是天才的idea, 其实最优秀的算法往往是简单有效的算法。

附上完整的算法代码:

 /*********************************StringHash.h*********************************/

 #pragma once

 #define MAXTABLELEN 1024 // 默认哈希索引表大小
//////////////////////////////////////////////////////////////////////////
// 哈希索引表定义
typedef struct _HASHTABLE
{
  long nHashA;
  long nHashB;
  bool bExists;
}HASHTABLE, *PHASHTABLE ; class StringHash
{
public:
  StringHash(const long nTableLength = MAXTABLELEN);
  ~StringHash(void);
private:
  unsigned long cryptTable[0x500];
  unsigned long m_tablelength; // 哈希索引表长度
  HASHTABLE *m_HashIndexTable;
private:
  void InitCryptTable(); // 对哈希索引表预处理
  unsigned long HashString(const string& lpszString, unsigned long dwHashType); // 求取哈希值
public:
  bool Hash(string url);
  unsigned long Hashed(string url); // 检测url是否被hash过
}; /*********************************StringHash.cpp*********************************/ #include "StdAfx.h"
#include "StringHash.h" StringHash::StringHash(const long nTableLength /*= MAXTABLELEN*/)
{
  InitCryptTable();
  m_tablelength = nTableLength;
  //初始化hash表
  m_HashIndexTable = new HASHTABLE[nTableLength];
  for ( int i = ; i < nTableLength; i++ )
  {
    m_HashIndexTable[i].nHashA = -;
    m_HashIndexTable[i].nHashB = -;
    m_HashIndexTable[i].bExists = false;
  }
} StringHash::~StringHash(void)
{
  //清理内存
  if ( NULL != m_HashIndexTable )
  {
    delete []m_HashIndexTable;
    m_HashIndexTable = NULL;
    m_tablelength = ;
  }
} /************************************************************************/
/*函数名:InitCryptTable
/*功 能:对哈希索引表预处理
/*返回值:无
/************************************************************************/
void StringHash::InitCryptTable()
{
  unsigned long seed = 0x00100001, index1 = , index2 = , i;   for( index1 = ; index1 < 0x100; index1++ )
  {
    for( index2 = index1, i = ; i < ; i++, index2 += 0x100 )
    {
      unsigned long temp1, temp2;
      seed = (seed * + ) % 0x2AAAAB;
      temp1 = (seed & 0xFFFF) << 0x10;
      seed = (seed * + ) % 0x2AAAAB;
      temp2 = (seed & 0xFFFF);
      cryptTable[index2] = ( temp1 | temp2 );
    }
  }
} /************************************************************************/
/*函数名:HashString
/*功 能:求取哈希值
/*返回值:返回hash值
/************************************************************************/
unsigned long StringHash::HashString(const string& lpszString, unsigned long dwHashType)
{
  unsigned char *key = (unsigned char *)(const_cast<char*>(lpszString.c_str()));
  unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE;
  int ch;   while(*key != )
  {
    ch = toupper(*key++);     seed1 = cryptTable[(dwHashType << ) + ch] ^ (seed1 + seed2);
    seed2 = ch + seed1 + seed2 + (seed2 << ) + ;
  }
  return seed1;
} /************************************************************************/
/*函数名:Hashed
/*功 能:检测一个字符串是否被hash过
/*返回值:如果存在,返回位置;否则,返回-1
/************************************************************************/
unsigned long StringHash::Hashed(string lpszString) {
  const unsigned long HASH_OFFSET = , HASH_A = , HASH_B = ;
  //不同的字符串三次hash还会碰撞的几率无限接近于不可能
  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 % m_tablelength,
  nHashPos = nHashStart;   while ( m_HashIndexTable[nHashPos].bExists)
  {
  if (m_HashIndexTable[nHashPos].nHashA == nHashA && m_HashIndexTable[nHashPos].nHashB == nHashB)
    return nHashPos;
  else
  nHashPos = (nHashPos + ) % m_tablelength;   if (nHashPos == nHashStart)
  break;
  }   return -; //没有找到
} /************************************************************************/
/*函数名:Hash
/*功 能:hash一个字符串
/*返回值:成功,返回true;失败,返回false
/************************************************************************/
bool StringHash::Hash(string lpszString)
{
  const unsigned long HASH_OFFSET = , HASH_A = , HASH_B = ;
  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 % m_tablelength,
  nHashPos = nHashStart;   while ( m_HashIndexTable[nHashPos].bExists)
  {
    nHashPos = (nHashPos + ) % m_tablelength;
    if (nHashPos == nHashStart) //一个轮回
    {
      //hash表中没有空余的位置了,无法完成hash
      return false;
    }
  }
  m_HashIndexTable[nHashPos].bExists = true;
  m_HashIndexTable[nHashPos].nHashA = nHashA;
  m_HashIndexTable[nHashPos].nHashB = nHashB;   return true;
}

暴雪HASH算法(转)的更多相关文章

  1. 暴雪hash算法

    你有一个非常大的字符串数组A,现在又有一个字符串B,需要你去检测B是否存在于A中.最简单粗暴的方法是遍历整个A,但是这个方法投入到实际应用时的运行速度是难以接受的.在没有与其他所有字符串比较前怎么知道 ...

  2. 暴雪的hash算法[翻译]

    原文来自:http://sfsrealm.hopto.org/inside_mopaq/chapter2.htm#hashes 促进历史进步的大多数契机都是在解决特定问题的过程中产生的,本文讨论一下M ...

  3. Hash冲突的解决--暴雪的Hash算法

    Hash冲突的解决--暴雪的Hash算法https://usench.iteye.com/blog/2199399https://www.bbsmax.com/A/kPzOO7a8zx/

  4. 21Hash算法以及暴雪Hash

    一:哈希表简介 哈希表是一种查找效率极高的数据结构,理想情况下哈希表插入和查找操作的时间复杂度均为O(1),任何一个数据项可以在一个与哈希表长度无关的时间内计算出一个哈希值(key),然后在常量时间内 ...

  5. 记录几个经典的字符串hash算法

    记录几个经典的字符串hash算法,方便以后查看: 推荐一篇文章: http://www.partow.net/programming/hashfunctions/# (1)暴雪字符串hash #inc ...

  6. 对一致性Hash算法,Java代码实现的深入研究

    一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...

  7. 一致性hash算法详解

    转载请说明出处:http://blog.csdn.net/cywosp/article/details/23397179     一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT) ...

  8. 一致性hash算法简介

    一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简单哈希 ...

  9. 分布式缓存技术memcached学习(四)—— 一致性hash算法原理

    分布式一致性hash算法简介 当你看到“分布式一致性hash算法”这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几 ...

随机推荐

  1. wince中测试驱动应用程序的实现

    这里建的工程是MFC的smart device,选择ARMV4I的指令集,不同的设备可能会有轻微的不同,不过大体实现是一样滴.还有,这里选的应用类型是dialog base. 1.应用监测内核动向 内 ...

  2. 【转】java调用webservice

    互联网上面有很多的免费webService服务,我们可以调用这些免费的WebService服务,将一些其他网站的内容信息集成到我们的Web应用中显示,下面就以获取天气预报数据和查询国内手机号码归属地为 ...

  3. Struts2中获取servlet API的几种方式

    struts2是一个全新的MVC框架,如今被广大的企业和开发者所使用,它的功能非常强大.这给我们在使用servlet 纯java代码写项目的时候带来了福音.但是一般来说,我们的项目不到一定规模并不需要 ...

  4. eclipse maven testng

    安装过程: 1.eclipse官网下载:

  5. selenium support

      org.openqa.selenium.support.ui.Select select = new org.openqa.selenium.support.ui.Select(driver.fi ...

  6. angularjs 表单验证(不完整版)

    针对项目实践表单验证总结: angular 的 form表单验证:form内需要novalidate取消默认验证,用ng自己的验证,form的名字是非常必要的 栗子:以注册为栗子,下面是注册的部分: ...

  7. C#与C++的区别!

    (1) 编译目标:C++代码经常编译成汇编语言.而C#则编译成中间语言(IL)它与Java的字节代码有些相似.IL随后在通过Iust-In-Time编译进程转换成本机的可执行代码.IL代码将作为一个装 ...

  8. 手Q兴趣号的价值在哪里

    拥有5.21亿月活跃用户,如果不做点什么东西出来,实在是浪费至极.如此庞大的用户量,如果能够将内容贡献出来,那将是恐怖的,QQ空间产品就是很好的佐证. QQ群让个体用户能够连接在一起,单个的用户关系链 ...

  9. java 多线程(threadlocal)

    package com.example; import java.util.Random; public class App { public static class MyRunnable1 imp ...

  10. ubuntu git 使用

    apt-get install git//ubuntu安装git mkdir -p /var/www/gitProj //创建文件夹 cd /var/www/gitProj //进入文件夹 git i ...