数据结构-散列表

一、散列函数

散列表是表示集合和字典的另一种有效方法,通过将关键码映射到表中某个位置来存储元素,然后根据关键码用同样的方法直接访问。

散列方法使用的转换函数即散列函数,散列函数不能太复杂。

散列函数定义域必须包括全部关键码,值域必须在0到m-1之间(m为散列表允许的地址数),通常关键码集合比散列地址集合大得多,因此散列函数是一种压缩映像函数。对不同关键码,通过散列函数的计算可能得到同一散列地址,称这些散列地址相同的不同关键码为同义词

如果key是从关键码集合中任意取出的一个关键码,散列函数应该能以同等概率0到m-1中每一个值

1.除留余数法

取一个不大于m,但最接近或者等于m的质数p作为除数

质数p不能是接近2的幂,否则散列函数值仅仅是关键码最低k位数的值

2.数学分析法

 

3.平方取中法

一般取散列表地址为8的某次幂即m=8r

先计算构成关键码的标识符的内码(每个字符用两个八进制数表示)平方,然后按照散列表的大小取中间r位

4.折叠法

把关键码自左向右分为位数相等的几部分,每一部分和散列表地址相等,最后一部分较短,叠加得到具有该关键码记录的散列地址。

一般关键码位数很多,而且关键码的每一位上数字分布大致比较均匀时使用。

有两种叠加方法:

1)移位法

把各部分最后一位对齐相加。

2)分界法

沿各部分分界来回折叠然后对齐相加。

二、溢出处理技术

将散列表HT的m个地址改为m个bucket,桶号与散列地址一一对应。桶的大小通常比较小,因此在桶内一般采用顺序搜索。

1.闭散列法开地址法

所有的桶都直接放在散列表数组中,并把该数组组织成环形结构。每个桶中只有一个元素。

因此容易产生堆积问题,不同探查序列的关键码占据了可利用的空桶,使得为寻找某一关键码需要经历不同的探查序列的元素。

找到一个元素的比较次数与当初将它存入时的探查次数相等。

不能随便物理删除表中已有元素,否则会影响其它元素的搜索。

1)线性探查法(相继溢出法)

2)二次探查法

散列表的大小TableSize必须是满足4k+3的质数

当表的长度TableSize为质数且表的装载因子α不超过0.5时,表的新项就一定可以插入,且任何一个位置不会被探查两次

装载因子α超过0.5时应将表长度扩大一倍

3)双散列法

双散列法的思路是应用伪随机探查方法,使用一个伪随机数产生器再散列函数

发生冲突时利用再散列函数ReHash(),根据元素的关键码key,计算该元素向后到达下一个桶的位移量位移量的取值与key的值有关,应当为小于地址空间大小TableSize且与其互质(公约数只有1的正整数。

最多经过m-1次探查会遍历表中所有位置回到H0

使用闭散列法组织的散列表如下所示:

const int DefaultSize=100;
enum KindOfStatus{Active,Empty,Deleted};    //元素分类(活动/空/删)
template <class E,class K>
class HashTable{
public:
    HashTable(const int d,int sz=DefaultSize);
    ~HashTable(){delete []ht; delete []info;}
    HashTable<E,K>& operator=(const HashTable<E,K>& ht2);
    bool Search(const K kl,E& el)const;
    bool Insert(const E& el);
    bool Remove(const K kl,E& el);
    void makeEmpty();
private:
    int divitor;       //散列函数的除数
    int CurrentSize,TableSize;     //当前桶数、最大桶数
    E *ht;      //散列表存储数组
    KindOfStatus *info;   //状态数组
    int FindPos(const K kl)const;
    int operator==(E& el){return *this==el;}
    int operator!=(E& el){return *this!=el;}
};

template <class E,class K>
HashTable<E,K>::HashTable(const int d, int sz) {
    divitor = d;
    TableSize = sz;
    CurrentSize = 0;
    ht = new E[TableSize];
    info = new KindOfStatus[TableSize];
    for (int i = 0; i < TableSize; i++) info[i] = Empty;
}

template <class E,class K>
int HashTable<E,K>::FindPos(const K kl)const {
    //使用线性探查法搜索在一个散列表中关键码与kl匹配的元素位置
    //搜索成功时移动次数与插入时移动次数相等,搜索不成功则返回插入位置,表满了则返回kl%divitor
    int i=kl%divitor;
    int j=i;
    do{
        if(info[j]==Empty || info[j]==Active && ht[j]==kl) return j;
        j=(j+1)%TableSize;  //当作循环表处理,找下一个空桶
    }while(j!=i);
    return j;     //转一圈回到开始点,表满了
}

template <class E,class K>
bool HashTable<E,K>::Search(const K kl, E& el) const {
    //使用线性探查法在根据关键码kl在散列表中搜索,如果存在则用引用参数el返回找到的值ht[i],并返回true。没找到或者表满了返回false
    int i=FindPos(kl);
    if(info[i]!=Active || ht[i]!=kl) return false; //可能info[i]==Empty或者表满了
    el=ht[i];
    return true;
}

template <class E,class K>
void HashTable::makeEmpty() {
    for (int i = 0; i <TableSize; i++) {
        info[i]=Empty;
    }
    CurrentSize=0;
}

template <class E,class K>
HashTable<E,K>& HashTable<E,K>::operator=(const HashTable<E, K> &ht2) {
    if(this!=&ht2){   //防止自我复制
        delete []ht;
        delete []info;
        TableSize=ht2.TableSize;
        ht=new E[TableSize];
        info=new KindOfStatus[TableSize];
        for (int i = 0; i <TableSize; i++) {
            ht[i]=ht2.ht[i];
            info[i]=ht2.info[i];
        }
        CurrentSize=ht2.CurrentSize;
    }
    return *this;
}

template <class E,class K>
bool HashTable<E,K>::Insert(const E &el) {
    K kl = el;
    int i = FindPos(kl);
    if(info[i]!=Active){  //书上这段程序有一个问题,如果元素不在ht[kl%divitor],且状态为delete,并不会插入。
        ht[i]=el;
        info[i]=Active;
        CurrentSize++;
        return true;
    }
    if(info[i]==Active && ht[i]==el){
        cout<<"表中已有此元素,不能插入!"<<endl;
        return false;
    }
    cout<<"表已满,不能插入!"<<endl; //表已满时i=FindPos(kl)=kl%divitor,此时不能插入
    return false;
}

//在闭散列情形下不能随便物理删除表中已有元素,否则会影响其它元素的搜索。
template <class E,class K>
bool HashTable<E,K>::Remove(const K kl, E &el) {
    //在ht表中找到删除元素key,返回true,并在引用参数el中得到它
    int i=FindPos(kl);
    if(info[i]==Active && ht[i]==kl){
        el=ht[i];
        info[i]=Deleted;
        CurrentSize--;
        return true;
    }
    else return false; //若表中找不到kl,或者它已经逻辑删除过,则返回false
}

二次探查法:

template <class E,class K>
int HashTable<E,K>::FindPos(const K kl){   //这里书上写得应该有点问题,我给改了一下
    int i = kl%divitor;
    int k = odd = 0;   //k为探查次数,odd为控制加减符号
    int save0 = save1 = 0;
    while(info[i]==Active && ht[i]!=kl || info[i]==Deleted){//info[i]==empty或者找到了kl则终止循环
        if(odd==0){
            save1 = i;
            i = save0;
            k++;
            i=(i+2*k-1)%TableSize; //(k-1)^2和k^2之差为2*k-1
            odd=1;
        }
        else{
            save0 = i;
            i = save1;
            i=(i-2*k+1)%TableSize;
            odd=0;
            if(i<0)i=i+TableSize;
        }
        return i;
        
}

template <class E,class K>
bool HashTable<E,K>::Insert(const E& el){
    //插入时必须保证表的装载因子不超过0.5,否则必须进行分裂
    K kl=el;
    int i=FindPos(kl),j,k;
    if(info[i]==Active) return false;
    ht[i]=el;
    info[i]=Active;
    if(++CurrentSize<TableSize/2) return true;
    E *OldHt=ht;
    KindOfStatus *oldinfo=info;
    int OldTableSize=TableSize;
    CurrentSize=0;
    TableSize=NextPrime(2*OldTableSize);    //求大于某数的第一个素数
    divitor=TableSize;
    ht=new E[TableSize];
    if(ht==NULL){
        cerr<<"存储分配失败!"<<endl;
        return false;
    }
    info=new KindOfStatus[TableSize];
    if(info==NULL){
        cerr<<"存储分配失败!"<<endl;
        return false;
    }
    for(j=0;j<TableSize;j++) info[j]=empty;
    for(i=0;i<TableSize;i++) if(oldinfo[i]==Active) Insert(OldHt[i]);
    delete []OldHt;
    delete []oldinfo;
    return true;
}


int NextPrime(int n){
    if(n%2==0) n++;    //偶数
    for(;!IsPrime(n);n+=2);
    return n;
}
    
int IsPrime(int n){
    for(int i=3;i*i<=n;i+=2) if(n%i==0) return 0;
    return 1;
}

2.开散列法(拉链法)

当散列表经常变动时最好不使用闭散列来处理冲突,改用开散列法。

以搜索平均长度n/m的同义词子表代替了搜索长度为n的顺序表,搜索速度加快。

使用开散列法的散列表如下所示:

template <class E,class K>
struct ChainNode{
    E data;
    ChainNode<E,K> *link;
}

template <class E,class K>
class HashTable{
public:
    HashTable(int d,int sz=defaultSize);
    ~HashTable(){delete []ht;}
    bool Search(const K kl,E& el);
    bool Insert(const E& el);
    bool Remove(const K kl,E& el);
private:
    int divisor;
    int TableSize;
    ChainNode<E,K> **ht;
    ChainNode<E,K> *FindPos(const K kl);
}

template <class E,class K>
HashTable<E,K>::HashTable(int d,int sz){
    divisor=d;
    TableSize=sz;
    ht=new ChainNode<E,K> *[sz];
    assert(ht!=NULL);
}

template <class E,class K>
ChainNode<E,K> *HashTable<E,K>::FindPos(const K& kl){
    //在散列表中搜索关键字为kl的元素,函数返回一个指向散列表中某个位置的指针。若元素不存在,返回NULL
    int j=kl%divisor;
    ChainNode<E,K> *p=ht[j];
    while(p!=NULL && p->data!=kl) p=p->link;
    return p;
}

 

转载于:https://www.cnblogs.com/yangyuliufeng/p/10713957.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值