哈希表

哈希表是一种 以常数平均时间执行插入、删除和查找操作的数据结构,但是哈希表一般不提供任何与排序相关的操作,如Findmax,FindMin,sort。

哈希表

散列表(Hash table,也叫哈希表),是根据关键字的值(Key value)而直接进行访问的数据结构。这里的关键字可能是数或者字符串。也就是说,通过将关键字的值映射到表中一个位置来查找。这个映射就做哈希函数,即有 Hash(key)=address 。知道key之后,通过哈希函数计算出地址,即可在O(1)的时间内完成查找,插入,删除等操作,可以看到这是非常高效的。这与数组链表等都不同,数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易,而哈希表则集中了他们的优点。但同样也有缺点,一种数据结构不可能没有缺点,不同的数据结构都有不同的应用场合,这也是它们的特点决定的。哈希表对排序相关的操作并不提供支持。

哈希函数

一个好的哈希函数所具有的的特点:计算简单+分布均匀(计算得到的散列地址分布均匀)
常用的哈希函数的构造方法:
直接寻址:H(key)=key 或 H(key) = a·key + b
数字分析法:在多组关键字中取重复较少的来作为key
除留取余法:Hash(key) = key % p,p一般取一个小于等于表长的素数。
另外还有:平方取中法、折叠法、随机数法、斐波那契散列法等。可参见Perfect hash function

哈希冲突

我们通过哈希函数来求得地址,但是由于内存等限制,我们不可能将地址范围取得非常非常大,这样如果只存储少量元素则是非常低效的。而关键字值key通常会具有某些相似性。这样就有可能使得两个不同关键字通过哈希函数计算出的哈希值产生相同的情况。这被称为哈希冲突。比如,我们常用的一个最简单的哈希函数——除留取余法,即 Hash(key) = key % p,p一般取一个素数。这样很容易就会得到一样的地址。所以我们需要处理哈希冲突。

虽然哈希冲突基本上是不可避免的,但是首先必须得选择一个好的哈希函数。

为了保证查找效率,存储一般都选择数组。这里介绍两种处理哈希冲突的方法:
分离链接法(拉链法):
将地址相同的元素链在前一个元素后面,使用链表,一个地址存放多个元素。如下图:

此处输入图片的描述

开放定址法
依然采用数组存储,但是在遇到冲突时,哈希函数调整为 Hi(key) = (Hash(key) + F(i)) % TableSize,其中函数F是冲突解决方法,F(0)=0,这样我们就可以依次尝试选择H0(key),H1(key),H2(key)等作为哈希函数。并将该元素放入第一个被选中的没有被占据的位置。
对于不同的F可以将其称作不同的方法,比如F(i)=i,线性探测法;F(i)=i2,平方探测法;F(i)=i·Hash2(key),双散列法。

这样做有一个问题,当删除其中的一些元素后,那些原来因为这个位置被填充而存储到其他位置的元素,就会面临查找时找不到的情况。所以我们一般使用懒惰删除,只是将其状态置为删除。并不真正从数组中删除(尽管在数组中并没有真正意义的删除)。所以我们需要将数组的类型定义为结构,并将元素状态封装在其中(可使用枚举变量)。

填充因子

λ = 已填充元素个数/表长,对于分离链接法,达到1左右最佳。对于开放定址法,不应使其超过0.5,否则性能可能会急剧下降。对于开放定址法,当 λ 达到0.5后,可以将其复制到一个更大的表中,这将花费O(n)的操作时间。称其为再哈希(rehash)。下面的第二个实现中实现了rehash。

实现

这里简要实现以下,哈希函数采用最简单的取余法,这里只是一个简单实现,没有考虑太多。

分离链接法实现:

//2018.3.20
//哈希表-分离链接法
//这次带头结点了,一点点空间而已,没必要增大编写难度 
#include<iostream>
using namespace std;
typedef int ElemType;
class HashTable
{
private:
    typedef int Index;
    typedef class ListNode
    {
    public:
        ElemType element;
        ListNode * next;
    } * Position, * List;
    int Table_Size;
    List * Lists;
private:
    Index Hash(ElemType key)
    {
        return key % Table_Size; 
    }
    Position Find(ElemType key);
public:
    HashTable(int table_size);
    ~HashTable();
    void Insert(ElemType key);
    void Delete(ElemType key);
    void Print();
};

int main(void)
{
    HashTable H(20);
    for(int i=0; i<100; i++)
    {
        H.Insert(i);
    }
    H.Print();
    for(int i=0; i<50; i++)
        H.Delete(i);
    H.Print();
    return 0;
}
HashTable::HashTable(int table_size)
{
    Table_Size = table_size;
    Lists = new Position[table_size];//Lists存放指向ListNode的指针 
    for(int i=0; i<table_size; i++)
    {
        Lists[i] = new ListNode;//we need a header ndoe
        Lists[i]->element = 0;
        Lists[i]->next = NULL;
    }
//  cout << "HashTable()" << endl;
}
HashTable::~HashTable()
{
    for(int i=0; i<Table_Size; i++)
    {
        Position p = Lists[i];
        while(p != NULL)
        {
            Position tmp = p;
            p = p->next;
            delete tmp;
        }
    }
    delete [] Lists;
}
HashTable::Position HashTable::Find(ElemType key)
{
    Position p = Lists[Hash(key)]->next;
    while(p != NULL && p->element != key)//判断顺序一定不能变 
        p = p->next;
    return p;
}
void HashTable::Insert(ElemType key)
{
    Position pos = Find(key);
    if(pos == NULL)//key不存在则在表头插入
    {
        List L = Lists[Hash(key)];
        Position p = new ListNode;
        p->element = key;
        p->next = L->next;
        L->next = p;
    }
    //存在则do nothing
}
void HashTable::Delete(ElemType key)
{
    Position pos = Find(key);
    if(pos != NULL)//存在则删除 
    {
        Position pre = Lists[Hash(key)];
        while(pre->next != pos)
            pre = pre->next;
        pre->next = pos->next;
        delete pos;
    }
}
//按照一定的规则将哈希表中元素分布打印出来 
void HashTable::Print()
{
    for(int i=0; i<Table_Size; i++)
    {
        Position p = Lists[i]->next;
        if(p == NULL)
            cout << "index:" << i << " No elements" << endl;
        else
        {
            cout << "index:" << i << " elements: "; 
            while(p != NULL)
            {
                cout << p->element << " ";
                p = p->next;
            }
            cout << endl;
        }
    }
}

  
开放定址法实现:

//2018.3.20
//hash table --开放定址法
//线性探测法,平方探测法,双散列法 
#include<iostream>
using namespace std;
typedef int ElemType;
const int Min_cell_num = 20;
class HashTable
{
private:
    enum Cell_State { legitimate, empty, deleted };//legitimate合法的 
    typedef unsigned int Index;
    typedef Index Position;
    class Cell
    {
    public:
        ElemType element;
        enum Cell_State info;
    };
    int Table_Size;
    Cell * Cells;
    int Elements_num;//已填充的元素个数,包括已删除的 
public:
    HashTable(const int table_size);
    ~HashTable();
    void Insert(const ElemType key);
    void Delete(const ElemType key);
    void Print() const;
    int Get_elements_num() const; 
private: 
    Position Find(const ElemType key);
    inline Index Hash(const ElemType key)
    {
        return key % Table_Size;
    }
    void ReHash();
};

int main(void)
{
    HashTable H(19);
    for(int i=0; i<100; i++)
    {
        H.Insert(i);
    }
    H.Print();

    int A[10] = {3,7,32,34,47,25,13,9,28,22};
    for(int i=0; i<10; i++)
        H.Insert(A[i]);
    H.Print();
    cout << H.Get_elements_num() << endl;
    return 0;
}

HashTable::HashTable(const int table_size)
{
    if(table_size >= Min_cell_num)
        Table_Size = table_size;
    else 
    {
        cout << "The table size is too small , already set Table_Size = " << Min_cell_num << endl;
        Table_Size = Min_cell_num; 
    }
    Cells = new Cell[Table_Size];
    for(int i=0; i<Table_Size; i++)//初始化必不可少 
        Cells[i].info = empty;
    Elements_num = 0;
}

HashTable::~HashTable()
{
    delete Cells;
}

HashTable::Position HashTable::Find(const ElemType key)
{
    Position current_pos = Hash(key);
    int offset = 0;
    while(Cells[current_pos].info != empty && Cells[current_pos].element != key)
    {
        //线性探测法 
        //current_pos ++;
        //平方探测法
        current_pos += 2 * ++offset -1; //此处采用递推关系f(i) = f(i-1)+2*i-1,f(0)=0 
        //双散列法
        //current_pos += (R-R%key);//需要事先定义一个素数R 
        if(current_pos >= Table_Size)
            current_pos -= Table_Size;//取模
    }
    return current_pos; 
}

void HashTable::Insert(const ElemType key)
{
    Position pos = Find(key);
    //这里可能存在关键字为key的元素,不存在或者存在且被删除就插入,存在合法就跳过 
    if(Cells[pos].info != legitimate)
    {
        if(Cells[pos].info == empty)
            Elements_num ++;
        Cells[pos].info = legitimate;
        Cells[pos].element = key;
    }
    if(Elements_num >= Table_Size/2)//填充因子达到0.5就再哈希 
        ReHash();
}

void HashTable::Delete(const ElemType key)
{
    Position pos = Find(key);
    if(Cells[pos].info == legitimate)
        Cells[pos].info == deleted;//存在且合法则将其置为删除状态 
}

void HashTable::Print() const
{
    for(int i=0; i<Table_Size; i++)
    {
        if(Cells[i].info == legitimate)
            cout << "Index: " << i << " element: " << Cells[i].element << endl;
        else
            cout << "Index: " << i << " no elements !" << endl;
    }
}
int HashTable::Get_elements_num() const
{
    return Elements_num;
}
//再哈希,当填充因子达到0.5时则将其复制到一个两倍大的表中
void HashTable::ReHash()
{
    Cell * Old_cells = Cells;
    int Old_size = Table_Size;
    Cells = new Cell [2*Table_Size+1] ;
    Table_Size = 2*Table_Size+1;
    Elements_num = 0;
    for(int i=0; i<Table_Size; i++)//初始化是很重要的,不初始化是会crash的 
    {
        Cells[i].info = empty;
    }
    for(int i=0; i<Old_size; i++)
    {
        if(Old_cells[i].info == legitimate)
            Insert(Old_cells[i].element);
    }
    delete Old_cells;
    cout << "The hash table has been rehashed , now the table size is " << Table_Size << endl; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值