散列函数:将关键码映射为散列表中适当存储位置
int Hash(const char *Key, int TableSize) //字符串关键字的散列函数构造——移位法
{
unsigned int H = 0; //散列函数值初始化为0
while(*Key!='\0') //位移映射
H = (H<<5) + *Key++;
return (H % TableSize);
}
散列查找的时间复杂度:O(1),与表的长度无关
只能用于对关键字的随机查找
装填因子 = 表中元素个数/ 散列表空间大小,常取0.5~0.8
平均查找长度ASL:是关于的函数
处理冲突的办法:开放定址法,分离链接法
开放定址法
-当冲突发生时,形成一个探查序列,沿此序列逐个探查,直到找到空的地址
-线性探测、二次探测、伪随机探测
数据存储
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;
}
}