数据结构篇——散列表(hash table)

本文详细介绍了散列表(hash table)的工作原理,包括散列函数的选择、解决冲突的策略如分离链接法、线性探测法、平方探测法、双散列和再散列,并通过具体例子阐述了这些方法的实现。此外,还提到了散列表在实际问题中的应用,如UVA 10887例题。
摘要由CSDN通过智能技术生成

目录

散列表

分离链接法

不用链表的方法

线性探测法

平方探测法

双散列

再散列

散列表相关例题

UVA 10887


散列表

散列表(hash table),是把关键值映射到表中到一个位置来存储和访问的。关键值为x,那么pos=f(x),我们把x存储在table[pos]的位置里。这里f(x)就是散列函数(hash function)。

有可能出现f(x)=f(y)的情况,这种叫做冲突。好的哈希函数应当减少冲突。但是冲突有时不可避免,如何解决冲突呢?

分离链接法

将冲突的元素保留在一个链表中,如下

查询时,首先利用hash函数找到在哪个位置,之后遍历相应的链表。

分离链接哈希表的类型声明:

class HashTable{
public:
    HashTable(int s){
        for(int i=0;i<s;i++){
            list<string> l;
            table.push_back(l);
        }
        size =s;
        currentSize = 0;
    }
    bool contains(string x);
    void clear();
    bool insert(string x);
    bool remove(string x);
private:
    vector<list<string> > table;
    int size ;
    int currentSize ;
    int hash(string x);
};

哈希函数

int HashTable::hash(string x){
    int hashvalue = 0;
    for(char ch:x)
        hashvalue = 37*hashvalue + ch;
    return hashvalue % size;
}

相关操作

//清空
void HashTable::clear(){
    for(auto & t:table)
        t.clear();
}
//判断包含某个字符串
bool HashTable::contains(string x){
    list<string> l = table[hash(x)];
    for(list<string>::iterator i= l.begin();i!=l.end();i++)
        if(*i==x)
            return true;
    return false;
}
//删除
bool HashTable::remove(string x){
    list<string> &l = table[hash(x)];
    list<string>::iterator i = l.begin();
    for(i = l.begin();i!=l.end();i++)
        if(*i == x)
            break;
    if(i==l.end())
        return false;
    l.erase(i);
    --currentSize;
    return true;
}
//插入
bool HashTable::insert(string x){
    list<string> &l = table[hash(x)];
    list<string>::iterator i = l.begin();
    for(i = l.begin();i!=l.end();i++)
        if(*i == x)
            return false;
    l.push_back(x);
    ++currentSize;
    return true;
}

不用链表的方法

使用链表时,要涉及到分配空间,需要一定时间,于是尝试使用不用链表的方法。当遇到冲突时,探测该位置后第f(i)个位置,如果还有冲突,接着探测,直到没有冲突,然后放入元素。比如:遇到冲突,检测该位置向后第f(1)个位置,如果还有冲突,检测该位置后的第f(2)个位置,直到没有冲突。

线性探测法

f是线性函数,比如f(i) = i,相当于逐个向后探测。例如分别插入32,42,52,它们都是2号位置,遇到冲突,使用f(i)=i探测,如下

平方探测法

线性探测因为是依次挨个向后探测的,所以会连成一片,平方探测法可以解决这个问题。平方探测的冲突函数是二次函数,通常f(i)=i^2. 还是依次插入32,42,52,如下

代码(以平方探测法为例)[1]

class HashTable{
public:
    HashTable(int s);
    bool contains(string x);
    void clear();
    bool insert(string x);
    bool remove(string x);
private:
    //这里用的是懒惰删除法,删除后标注Deleted
    enum Nodetype {Active,Empty,Deleted};
    //哈希表中每个格存放内容和标注
    typedef struct HashNode{
        string element;
        Nodetype info;
        HashNode(string e,Nodetype i){
            element = e;
            info = i;
        }
    }HashNode;
    vector<HashNode> table;
    int size ;//size是表多大
    int currentSize ;//当前表有多少个元素
    int hash(string x);
    int findpos(string x);
    bool isActive(int pos);
};
HashTable::HashTable(int s){
    size = s;
    for(int i=0;i<s;i++)
        table.push_back(HashNode("",Empty));
    currentSize = 0;
}
bool HashTable::isActive(int pos){
    return table[pos].info==Active;
}
//找到第一个为Empty或者内容为x的位置
int HashTable::findpos(string x){
    int pos = hash(x);
    int offset = 1;
    while(table[pos].info!=Empty && table[pos].element!=x){
        pos = (pos + (int)pow(offset,2) - (int)pow(offset-1,2)) % size;
        offset++;
    }
    return pos;
}
void HashTable::clear(){
    currentSize = 0;
    for(auto & t:table)
        t.info = Empty;
}
bool HashTable::contains(string x){
    return isActive(findpos(x));
}
//删除
bool HashTable::remove(string x){
    int pos = findpos(x);
    if(isActive(pos)){
        table[pos].info = Deleted;
        currentSize--;
        return true;
    }
    else
        return false;
}
//插入
bool HashTable::insert(string x){
    int pos = findpos(x);
    //表中已经有x了,return false
    if(isActive(pos))
        return false;
    table[pos].element = x;
    table[pos].info = Active;
    currentSize++;
    return true;
}
//哈希函数
int HashTable::hash(string x){
    int hashvalue = 0;
    for(char ch:x)
        hashvalue = 37*hashvalue + ch;
    return hashvalue % size;
}

双散列

前边的f有f(i)=i,有f(i)=i^2,双散列是f(i)=i*hash2(x),就是说当发生冲突时,我们分别探测hash2(x), 2*hash2(x), 3*hash2(x)等位置。常用的hash2函数有hash2(x) = R - (x mod R),R是小于表大小的素数[2]。依然以32,42,52为例。假如选择R = 3,那么hash2(x) = R-(x mod R) = 3 - (x mod 3),

再散列

当哈希表装填的太满时,那么解决冲突时间就会很长,而且可能插入会失败。所以我们进行再散列操作,一种解决方式是新建立一个大约两倍大的表,扫描旧表中的元素,重新装填。判断是否要重新装填,我们可以判断元素数量大于表的一半大小。或者当表到达某一特定的装填因子时再哈希,装填因子=元素数量/表长。

代码

void HashTable::rehash(){
    vector<HashNode> temp_table = table;
    table.resize(2*temp_table.size());
    table.clear();
    for( HashNode N:temp_table){
        if(N.info == Active)
            insert(N.element);
    }
}

散列表相关例题

UVA 10887

#include<iostream>
#include<math.h>
#include<cstring>
#include<map>
using namespace std;
int main(){
    int a,b;
    int cases;
    cin>>cases;
    int counts = 1;
    while(cases--){
        map<string,int> m;
        cin>>a>>b;
        int num=a*b;
        char s1[a][20],s2[b][20];
        getchar();
        for(int i=0;i<a;i++)
            fgets(s1[i],20,stdin);
        for(int i=0;i<b;i++)
            fgets(s2[i],20,stdin);
        for(int i=0;i<a;i++){
            for(int j=0;j<b;j++){
                string str1(s1[i],strlen(s1[i])-1),str2(s2[j],strlen(s2[j])-1);
                //在现有的表中,检测到了某个字符串,那么num-1
                if(m.find(str1+str2)!=m.end())
                    num--;
                m[str1+str2] = 1;
            }
        }
        printf("Case %d: %d\n",counts++,num);
    }
    return 0;
}

 

 

[1]

[2]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值