散列查找的实现

散列函数:将关键码映射为散列表中适当存储位置

int Hash(const char *Key, int TableSize) //字符串关键字的散列函数构造——移位法
{
    unsigned int H = 0; //散列函数值初始化为0
    while(*Key!='\0') //位移映射
        H = (H<<5) + *Key++;
    return (H % TableSize);
}

散列查找的时间复杂度:O(1),与表的长度无关

只能用于对关键字的随机查找

装填因子\alpha = 表中元素个数/ 散列表空间大小,常取0.5~0.8

平均查找长度ASL:是关于\alpha的函数

处理冲突的办法:开放定址法,分离链接法


开放定址法

-当冲突发生时,形成一个探查序列,沿此序列逐个探查,直到找到空的地址

-线性探测、二次探测、伪随机探测

数据存储

typedef int ElementType;
typedef int Index; //散列地址勒烯
typedef Index Position; //数据所在位置和散列地址是同一类型
typedef enum { //散列单元状态类型
    Legitimate, //合法元素
    Empty, //空单元
    Deleted //已删除元素
}EntryType;
typedef struct HashEntry Cell; //散列表单元类型
struct HashEntry {
    ElementType Data; //存放元素
    EntryType Info; //单元状态
};
typedef struct TblNode *HashTable; //散列表类型
struct TblNode { //散列表结点定义
    int TableSize; //表的最大长度
    Cell *Cells; //存放散列单元数据的数组
};

散列表的创建

int NextPrime(int N) //返回大于N且不超过MAXTABLESIZE的最小素数,用作散列表的空间大小
{
    int i,p;
    p = (N%2)?N+2:N+1; //从大于N的下一个奇数开始
    while(p <= MAXTABLESIZE) {
        for(i = (int)sqrt(p); i>2; i--)
            if (!(p%i))
                break; //p不是素数
        if(i==2)
            break; //for循环正常结束,说明p是素数
        else p += 2; //否则试探下一个奇数
    }
    return p;
}

HashTable CreateTable(int TableSize)
{
    HashTable H;
    int i;
    H = (HashTable)malloc(sizeof(struct TblNode));
    H->TableSize = NextPrime(TableSize); //保证散列表最大长度是素数
    H->Cells = (Cell*)malloc(H->TableSize*sizeof(Cell)); //声明单元数组
    for (i = 0; i<H->TableSize; i++) //初始化单元状态为空单元
        H->Cells[i].Info = Empty;
    return H;
}

散列表的查找——平方探测法

Position Find(HashTable H, ElementType Key)
{
    Position CurrentPos, NewPos;
    int CNum = 0; //记录冲突次数
    NewPos = Hash(Key, H->TableSize); //散列初始位置
    CurrentPos = NewPos;
    while(H->Cells[NewPos].Info!=Empty && H->Cells[NewPos].Data!=Key) {
        //当该位置的单元非空,并且不是要找的元素时,发生冲突
        //统计一次冲突,并判断奇偶次
        if(++CNum % 2) { //奇数次冲突
            NewPos = CurrentPos + (CNum+1)*(CNum+1)/4;
            if(NewPos >= H->TableSize)
                NewPos = NewPos % H->TableSize; //调整为合法位置
        }
        else { //偶数次冲突
            NewPos = CurrentPos - CNum*CNum/4;
            while(NewPos<0)
                NewPos += H->TableSize; //调整为合法位置
        }
    }
    return NewPos; //Key的位置或者是一个空单元的位置
}

散列表的插入

bool Insert(HashTable H, ElementType Key) //插入关键字Key
{
    Position Pos = Find(H, Key); //检查Key是否已存在
    if(H->Cells[Pos].Info!=Legitimate) { //如果这个单元没有被占,说明Key可以插入在此
        H->Cells[Pos].Info = Legitimate; //将此单元状态改为被占用
        H->Cells[Pos].Data = Key; //存入关键字
        return true;
    }
    else {
        printf("键值已存在");
        return false;
    }
}

散列表的删除

bool Delete(HashTable H, ElementType Key) //删除关键字Key
{
    Position CurrentPos, NewPos;
    int CNum = 0; //记录冲突次数
    NewPos = Hash(Key, H->TableSize); //散列初始位置
    CurrentPos = NewPos;
    while(H->Cells[NewPos].Info!=Empty && H->Cells[NewPos].Data!=Key) {
        //当该位置的单元非空,并且不是要找的元素时,发生冲突
        //统计一次冲突,并判断奇偶次
        if(++CNum % 2) { //奇数次冲突
            NewPos = CurrentPos + (CNum+1)*(CNum+1)/4;
            if(NewPos >= H->TableSize)
                NewPos = NewPos % H->TableSize; //调整为合法位置
        }
        else { //偶数次冲突
            NewPos = CurrentPos - CNum*CNum/4;
            while(NewPos<0)
                NewPos += H->TableSize; //调整为合法位置
        }
    }
    if(H->Cells[NewPos].Data == Key) { //找到该关键字则删除
        H->Cells[NewPos].Info = Deleted //修改位置信息为被删除
        return true;
    }
    else { //没找到关键字
        print("该关键字不存在,输入错误\n")
        return false; //返回错误
    }
}

分离链接法

将所有关键字为同义词的记录存储在一个单链表中,用一维数组存储头指针

数据结构定义

- 表头不存储数据

typedef char ElementType[KEYLENGTH+1]; //#define KEYLENGTH 15 关键字字符串的最大长度
typedef int Index; //散列地址类型
//单链表结点的定义
typedef struct LNode *PtrToLNode;
struct LNode {
    ElementType Data;
    PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;

typedef struct TblNode *HashTable; //散列表类型
struct TblNode { //散列表结点定义
    int TableSize; //表的最大长度
    List Heads; //指向链表头结点的数组
};
HashTable H; //表头不存储数据

散列表的结构如下图所示 

 初始化散列表

int NextPrime(int N) //返回大于N且不超过MAXTABLESIZE的最小素数
{
    int i,p;
    p = (N%2)?N+2:N+1; //从大于N的下一个奇数开始
    while(p <= KEYLENGTH) {
        for(i = (int)sqrt(p); i>2; i--)
            if (!(p%i))
                break; //p不是素数
        if(i==2)
            break; //for循环正常结束,说明p是素数
        else p += 2; //否则试探下一个奇数
    }
    return p;
}

HashTable CreateTable(int TableSize)
{
    HashTable H;
    int i;
    H = (HashTable)malloc(sizeof(struct TblNode)); //申请散列表头结点空间
    H->TableSize = NextPrime(TableSize); //保证散列表最大长度是素数
    H->Heads = (List)malloc(H->TableSize*sizeof(struct LNode)); //动态分配散列表头结点数组
    for (i = 0; i<H->TableSize; i++) { //初始化表头结点
        H->Heads[i].Data[0] = '\0';
        H->Heads[i].Next = NULL;
    }
    return H;
}

散列表的查找

Position Find(HashTable H, ElementType Key)
{
    Position P;
    Index Pos;
    Pos = Hash(Key, H->TableSize); //散列初始位置
    P = H->Heads[Pos].Next; //从该链表的第一个结点开始
    while(P && strcmp(P->Data, Key)) //字符串大小的比较,如果相同则结果为0
        P = P->Next;
    return P;
}

散列表的插入

bool Insert(HashTable H, ElementType Key)
{
    Position P, NewCell;
    Index Pos;
    P = Find(H, Key);
    if(!P) { //关键词未找到则可以插入
        NewCell = (Position)malloc(sizeof(struct LNode));
        strcpy(NewCell->Data, Key); //把字符串Key复制给NewCell->Data
        Pos = Hash(Key, H->TableSize); //初始散列位置
        //头插法:将NewCell插入为H->Heads[Pos]链表的第一个结点
        NewCell->Next = H->Heads[Pos].Next;
        H->Heads[Pos].Next = NewCell;
        return true;
    }
    else {
        printf("键值已存在");
        return false;
    }
}

散列表的删除

bool Delete(HashTable H, ElementType Key)
{
    Position TempCell;
    Index Pos;
    TempCell = Find(H, Key);
    if(TempCell) { //找到关键词则可以删除
        Pos = Hash(Key, H->TableSize); //初始散列位置
        H->Heads[Pos].Next = TempCell->Next;
        free(TempCell);
        return true;
    }
    else {
        printf("键值不存在\n");
        return false;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kentos(acoustic ver.)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值