数据结构学习笔记(十)哈希表(散列表)

一、哈希表(散列表)

        散列表,也称哈希表,是一种实现字典操作的有效数据结构。它用一个散列函数把一个数对映射到一个哈希表的具体位置。如果数对p 的关键字是k,散列函数为f,那么理想情况下,p在散列表中的位置为 f(k)。

        暂时假定散列表的每一个位置最多能够存储一个记录,这种情况也称为直接寻址表。当关键字的全域U比较小时,直接寻址是一种简单有效的技术。直接寻址表如下图11-1所示。

        数据结构学习笔记(十)哈希表(散列表)_第1张图片

        但是如果关键字的范围太大,使用上面的技术就有些不太合适。因此我们可以找到一个散列函数 f  可以把不同的关键字 k 映射到相同的地方进行存储,如下图11-2所示。当遇到不同的关键字映射到一个地方时,我们称之为发生冲突。我们可以采用链接法和开放寻址法来解决冲突的问题。

         数据结构学习笔记(十)哈希表(散列表)_第2张图片

        在链接法中,把散列到同一个槽的所有元素都放在一个链表中,而在哈希表的槽中存放的是一个指针,它指向存储所有散列到该位置的元素的链表的表头。如果不存在这样的元素,槽中为NULL。链接法解决冲突如下图:

         数据结构学习笔记(十)哈希表(散列表)_第3张图片

      在开放寻址法中,所有元素都要存放在散列表中。也即是说,每一个表项或包含一个元素,或包含空。当查找某个元素时,要系统的检查所有的表项,直到找到所需的元素,或者最终查明不在表内。不像链接法,这里既没有链表,也没有元素存放在散列表外。因此在开放寻址法中,散列表可能会被装满。为了找到插入元素的位置,当出现冲突时,我们要根据某种方式检查散列表,或称之为探查,直到找到一个空槽来放置待插入的关键字为止。关于探查的方法,在下一节讨论

二、散列函数

      一个好的散列函数应尽可能满足均匀散列的特点:每一个关键字都被等可能的散列到m个槽位中的任何一个,并且与其他关键字已散列到哪个槽位无关。

       除法散列法,通过取 k 除以 m 的余数,将关键字散列到 m 个槽位中,散列函数为 h (k) = k mod m。这种方式m的取值非常重要,一个好的 m 取值为一个(不太接近2的整数幂)的素数。

      乘法散列法包含两个步骤。第一步,用关键字 k  乘上常数 A(0

      全域散列法利用随机的思想,通过随机的选择散列函数,使之独立于要存储的关键字。一个全域散列的函数为 h(k) =(  (  ak+b ) mod p  ) mod m。

       讨论完散列函数,在讨论一下开放寻址法。为了确定要探查哪些槽,我们对散列函数加以扩充,使之包含探查号(从0开始)以作为其第二个输入参数。这样,散列函数就变为 h ( k, i ) 其中i 值取为 0、1 、2、3.……。

        我们给定一个普通的散列函数 h',称之为辅助散列函数,线性探查方法采用的散列函数为 h ( k,i ) = ( h'(k) + i  )  mod  m。给定一个关键字k,首先探查 T [  h'(k) ] ,即由辅助散列函数给出的槽位。在探查T [  h'(k)+1 ] 以此类推,直到槽 T [ m-1 ]。然后又绕回到T[0],直到 T [  h'(k) -1 ]。

          二次探查采用不同的方式,其散列函数为 h ( k,i ) = ( h'(k) + a*i + b*i^2  )  mod  m。给定一个关键字k,首先探查 T [  h'(k) ] ,后续的探查要加上一个偏移量,该偏移量以二次的方式依赖于探查序号i。

        双重探查是用于开放寻址法最好的方法之一,其 散列函数为 h ( k,i ) = ( h₁(k) + i*h₂(k)  )  mod  m。这里有两个辅助散列函数。这里两个辅助散列函数的取值非常重要。

三、哈希表的C++实现

       例子中使用简单的除法散列法进行散列,然后使用开放寻址法解决冲突,探查使用线性探查。具体的C++实现如下:

// 开放寻址法————线性探查,    除法散列

#pragma once
#include 
#include 

using namespace std;

 // mapping functions from K to nonnegative integer
template  class convert;

template<>
class convert
{
   public:
      size_t operator()(const string theKey) const
      {//把关键字 theKey 转换为一个非负整数
         unsigned long Value = 0; 
         int length = (int) theKey.length();
         for (int i = 0; i < length; i++)
            Value = 5 * Value + theKey.at(i);
    
         return size_t(Value);
      }
};

template<>
class convert
{
   public:
      size_t operator()(const int theKey) const    {return size_t(theKey);}
};

template<>
class convert
{
   public:
      size_t operator()(const long theKey) const     {return size_t(theKey);}
};


//哈希表的实现

template
class hashTable
{
   public:
	   //构造函数和析构函数
      hashTable(int theDivisor = 11);
      ~hashTable(){delete [] table;}
	  //成员函数
      bool empty() const {return dSize == 0;}
      int size() const {return dSize;}
      pair* find(const K&) const;
      void insert(const pair&);
      void output(ostream& out) const;

   protected:
      int search(const K&) const;
      pair** table;  // 散列表使用一维数组表示
      convert hash;              // 把类型k映射到一个非负整数
      int dSize;                 // 字典数对的个数
      int divisor;               // 散列函数的除数,即哈希表的槽的数目
};

template
hashTable::hashTable(int theDivisor)
{
   divisor = theDivisor;
   dSize = 0;

   // 分配和初始化散列表数组
   table = new pair* [divisor];
   for (int i = 0; i < divisor; i++)
      table[i] = NULL;
}

// 搜索一个公开地址散列表,查找关键字为theKey的数对
 // 如果匹配的数对存在,返回他的位置,
//否则,如果散列表不满,则返回关键字为theKey的数对可以插入的位置
//如果表满,也会返回一个位置,这个位置是探查时查找的第一个位置
template
int hashTable::search(const K& theKey) const
{
   int i = (int) hash(theKey) % divisor;  // 起始槽,除法散列法
   int j = i;    //从起始槽开始探查
   do
   {
      if (table[j] == NULL || table[j]->first == theKey)
         return j;
      j = (j + 1) % divisor;  // 下一个槽位
   } while (j != i);          // 是否返回起始槽

   return j;  // 表满,返回起始槽;起始槽恰好是要查找的,也要返回起始槽
}

//返回匹配数对的指针,如果数对不存在返回NULL
template
pair* hashTable::find(const K& theKey) const
{
   // 搜索散列表
   int b = search(theKey);

   // 判断 table[b]是否是匹配数对
   if (table[b] == NULL || table[b]->first != theKey)
      return NULL;           // 没有找到

   return table[b];  // 找到匹配数对
}

//把数对thePair 插入字典,若存在关键字相同的数对,则覆盖,若表满则抛出异常
template
void hashTable::insert(const pair& thePair)
{
   // 搜索散列表,查找匹配的数对
   int b = search(thePair.first);

   // 检查匹配的数对是否存在
   if (table[b] == NULL)
   {
      // 没有匹配数对而且表不满
      table[b] = new pair (thePair);
      dSize++;
   }
   else
   {// 检查是否有重复的关键字数对或是否表满
      if (table[b]->first == thePair.first)
      {//有重复的关键字数对,修改 table[b]->second
         table[b]->second = thePair.second;
      }
      else // 表满
	  {
		  cerr<<"The Table if full"<
void hashTable::output(ostream& out) const
{
   for (int i = 0; i < divisor; i++)
      if (table[i] == NULL)
         cout << "NULL" << endl;
      else
         cout << table[i]->first << " "
              << table[i]->second << endl;
}
template 
ostream& operator<<(ostream& out, const hashTable& x)
   {x.output(out); return out;}


       测试代码:

// test hash table class
#include 
#include "hashTable.h"

using namespace std;

void main()
{
   hashTable z(11);
   pair p;

   // test insert
   p.first = 2; p.second = 10;
   z.insert(p);
   p.first = 10; p.second = 50;
   z.insert(p);
   p.first = 24; p.second = 120;
   z.insert(p);
   p.first = 32; p.second = 160;
   z.insert(p);
   p.first = 3; p.second = 15;
   z.insert(p);
   p.first = 12; p.second = 60;
   z.insert(p);
   cout << "The dictionary is " << endl << z << endl;
   cout << "Its size is " << z.size() << endl;

   // test find
   cout << "Element associated with 2 is " << z.find(2)->second << endl;
   cout << "Element associated with 10 is " << z.find(10)->second << endl;
   cout << "Element associated with 12 is " << z.find(12)->second << endl;

}


你可能感兴趣的:(数据结构,数据结构)