【数据结构与算法分析】第五章 散列

【数据结构与算法分析】第五章 散列

散列是一种用于以常数平均时间执行插入、删除和查找技术。

1.散列函数

每个关键字被映射到从0到TableSize-1这个范围中的某个数,并放到适当的单元中。(TableSize为散列表的大小,表的大小为素数)

一种简单的散列函数:把字符串的ASCII码值加起来

typedef unsigned int Index;
Index Hsh(const char *key,int TableSize)
{
   unsigned  int HashVal=0;
    while(*key!=0)
        HashVal+=*key++;
    return HashVal % TableSize;
}

一种好的散列函数:Horner法则

Index Hash(const char *key,int TableSize)
{
    unsigned  int HashVal=0;
    while(*key!=0)
        HashVal=(HashVal<<5)+*key++;//向左移动5位,即乘以32
    return HashVal % TableSize;
}

如果当一个元素被插入时另一个元素已经存在(散列值相同),那么就产生一个冲突,消除冲突的方法:分离链接法开放定址法

2.分离链接法

将散列到同一值得所有元素保留到一个表中。这些表都有表头。

例:散列表大小为10,散列函数就是Hash(key,10)= key % 10,每个散列值均有一个表,如key=16,则散列值为6,key=36,散列值为6,他们的处于同一链表中

分离链接散列表的类型声明:

#ifndef _Hash_H
struct ListNode;
typedef struct ListNode *Position;
struct HashTb;
typedef struct HashTb *HashTable;
HashTable InitializeTable(int TableSize);
void DestroyTable(HashTable H);
Position Find(ElementType Key,HashTable H);
void Insert(ElementType Key,HashTable H);
ElementType Retrieve(Position P);
#endef
struct ListNode
{
    ElementType Element;
    Position Next;
}
typedef Position List;
struct HashTb
{
    int TableSize;
    List *TheLists;
}

分离链接散列表的初始化例程:

HashTable InitializeTable(int TableSize)
{
    HashTable H;
    int i;
    if(TableSize < MinTableSize)
    {
        Error("Table size too small");
        return NULL;
    }
    H=malloc(sizeof(struct HashTb));//分配列散表空间
    if(H=NULL)
        FatalError("out of space");
    H->TableSize=NextPrime(TableSize);//设置表的大小为一个素数
    H->TheLists=malloc(sizeof(List)*H->TableSize);//分配链表的空间
    if(H->TheLists==NULL)
        FatalError("out of space");
    for(i=0;i<H->TableSize;i++)//分配每个链表的表头空间
    {
        H->TheLists[i] = malloc(sizeof(struct ListNode));
        if(H->TheLists[i]==NULL)
            FatalError("out of space");
        else
            H->TheLists[i]->Next = NULL;   
    }
    return H;
}

分离链接散列表的Find例程:

Position Find(ElementType Key,HashTable H)
{
    Posion P;
    List L;
    L=H->TheLists[Hash(key,H->TableSize)];//散列值相同的表,L为表头
    P=L->Next;//表的第二个元素的位置
    while(P!=NULL&&P->Element!=key)//如果有第二个元素
        P=P->Next;
    return P;//返回P的值,为NULL则无重复,否则重复
}

分离链接散列表的Insert例程:

void Insert(ElementType Key,HashTable H)
{
    Position Pos,NewCell;
    List L;
    Pos = Find(Key,H);//查看是否存在散列值
    if(Pos==NULL)//散列值不存在
    {
        NewCell=malloc(sizeof(struct ListNode));//分配链表中一个元素的内存
        if(NewCell==NULL)
            FatalError("out of space");
        else
        {
            L=H->TheLists[Hash(Key,H->TableSize)];//散列表下的一个链表,L为表头
            NewCell->Next=L->Next;//
            L->Next=NewCell;//向链表中插入一个值
            NewCell->Element=Key;//向前插入,插入链表中的最前位置
        }
    }
}

插入如图所示:插入值为56

3.开放定址法

定义散列表的装填因子为散列表中的元素个数与散列表大小的比值,分离链接散列表的装填因子为1.

开放定址散列法不需要使用链表,所需要的表较大,其装填因子低于0.5,三个开放定址的冲突解决方法:线性探测法,平方探测法,双散列

开放定址散列表的类型声明:

#ifndef _HashQuad_H
typedef unsigned int Index;
typedef Index Position;
struct HashTb;
typedef struct HashTb *HashTable;
HashTable InitializeTable(int TableSize);
void DestroyTable(HashTable H);
Position Find(ElementType Key,HashTable H);
void Insert(ElementType Key,HashTable H);
ElementType Retrieve(Position P,HashTable H);
HashTable Rehash(HashTable H);
#endef
​
enum KindOfEntry{Legitimate,Empty,file};
struct HashEntry
{
    ElementType Element;
    enum KindOfEntry Info;
};
typedef struct HashEntry cell;
struct HashTb
{
    int TableSize;
    Cell *TheCells;
}

初始化开放定址散列表:

HashTable InitializeTable(int TableSize)
{
    HashTable H;
    int i;
    if(TableSize < MinTableSize)
    {
        Error("tablesize too small");
        return NULL;
    }
    H=malloc(sizeof(struct HashTb));//为散列表分配内存
    if(H==NULL)
        FatalError("out of space");
    H->TableSize=NextPrime(TableSize);//设置散列表的大小为一个素数
    H->TheCells=malloc(sizeof(Cell)*H->TableSize);//分配散列表的表项单元的数组
    if(H->TheCell==NULL)
         FatalError("out of space");
    for(i=0;i<H->TableSize;i++)//将每个表项单元的数组设置为Empty
        H->TheCells[i].Info=Empty;
    return H;
}

3.1线性探测法

无冲突时Hash(X)=X mod TableSize

有冲突时,F(i)=i,F为冲突解决方法

例:将关键字{89,18,49,58,69}插入到一个散列表中

插入89时,无冲突,正常插入

插入18时,无冲突,正常插入

插入49时,与89产生一个冲突,F(1)=1,放入9下一个空闲位置,即地址0

插入58时,与18产生冲突,往下一个地址移动依次与89,49冲突,然后找到空地址,即地址1

插入69时,与89产生冲突,依次向后一个一个找空位置,直到找到地址2

3.2平方探测法

无冲突时Hash(X)=X mod TableSize

有冲突时,F(i)=i*i,F为冲突解决方法

例:将关键字{89,18,49,58,69}插入到一个散列表中

插入89时,无冲突,正常插入

插入18时,无冲突,正常插入

插入49时,与89产生一个冲突,F(1)=1,放入9下一个空闲位置,即地址0

插入58时,与18产生冲突,F(1)=1,探测相邻的单元,发生冲突,F(2)=2*2=4,探测18后的第四个位置,即位置2,找到空地址

插入69时,与89产生冲突,F(1)=1,探测相邻的单元,发生冲突,F(2)=2*2=4,探测89后的第四个位置,即位置3,找到空地址

使用平方探测散列法的Find例程:

Position Find(Element key,HashTable H)
{
    Position CollitionPos;
    int CollitionNum;
    CollitionNum=0;
    CollitionPos=Hash(key,H->TableSize);//返回散列值
    while(H->TheCells[CollitionPos].Info!=Empty&&H->TheCells[CollitionPos].Element!=key)//如果冲突,1次:CollitionPos=1,2次CollitionPos=4,3次:CollitionPos=9...
    {
        CollitionPos +=2 * ++CollitionNum - 1;//公式:F(i)=F(i-1)+2i-1
        if(CollitionPos>=H->TableSize)
            CollitionPos-=H->TableSize;
    }
    return CollitionPos;//返回key应插入的位置
}

使用平方探测散列法插入:

void Insert(ElementType key,HashTable H)
{
    Position Pos;
    Pos=Find(key,H);//Pos为应该插入的位置
    if(H->TheCells[Pos].Info!=Legitimate)//合法
    {
        H->TheCells[Pos].Info=Legistimate;
        H->TheCells[Pos].Element=key;
    }
}

3.3双散列

无冲突时Hash(X)=X mod TableSize

有冲突时,F(i)=i*hash2(X),hash2(X)=R-(X mod R),R为小于TableSize的素数,产生第一个冲突时,F(1)=hash2(X),产生第二个冲突时,F(2)=2hash2(X)...

例:将关键字{89,18,49,58,69}插入到一个散列表中,选择R为7

插入89时,无冲突,正常插入

插入18时,无冲突,正常插入

插入49时,与89冲突,hash2(49)=7-(49 % 7)=7-0=7,探测89后的第7个位置,即位置6,检测位置为空,则将49插入

插入58时,与18冲突,hash2(58)=7-2=5,探测18后的第5个位置,即位置3,检测到位置为空,则将58插入

插入69时,与89冲突,hash2(69)=7-6=1,探测89后的第一个位置,即位置0,检测到位置为空,则将69插入

插入60时,与69冲突,hash2(60)=7-4=3,探测69后的第3个位置,即位置位置3,检测到冲突,2hash(2)=6,探测69后的第6个位置,即位置6,产生冲突,3hash2(60)=9,检测到冲突,然后检测为2

4.再散列

建立另一个大约两倍大的表(而且使用一个相关的新散列函数),扫描整个原始散列表,计算每个元素的新散列值并将其插入到新表中。

例:将13、15、24和6插入大小为7的开放定址散列表中,使用线性探测法解决冲突,插入结果如图所示

若将23插入表中,如图

插入23后的表将有超过70%的单元是满的,表填的过满,所有我们将建立一个新的表,取表的大小为17(原表两倍后的第一个素数),新的散列函数为hash(X)=X mod 17,扫描原表,将元素6,15,23,24,13插入新表中.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值