第1关:顺序查找
任务描述
本关任务:实现顺序查找。
相关知识
为了完成本关任务,你需要掌握:1.静态查找表的类型定义,2.顺序查找。
静态查找表的类型定义
#define MAXSIZE 100
typedef int KeyType; /*关键字类型*/
typedef struct
{
KeyType key;
/*InfoType otherinfo;*/
}RedType; /*记录类型*/
typedef struct
{
RedType r[MAXSIZE+1]; /*r[0]闲置或用作"监视哨"*/
int length;
}SSTable; /*静态查找表的类型定义*/
顺序查找
算法思想:从表的一端(第一个或最后一个)开始,逐个进行比较。 适用:顺序表、链表、有序表、无序表均可。
编程要求
在右侧编辑器中补充代码,完成Search_Seq
函数,以实现顺序查找。具体要求如下:
* Search_Seq:在表中查找其关键字等于key的记录,若找到返回该记录在表中位置,否则返回0。
测试说明
可在右侧文件夹中查看step1/Main.cpp
文件,以便于你的操作。
平台会对你编写的代码进行测试。
输入说明: 第一行输入若干记录的关键字,以-1标志结束。 第二行输入待查找的关键字。 输出说明: 第一行输出静态查找表L中各记录的关键字。 第二行输出查找的结果,如果找到,则输出该记录在表中的位置,否则输出not find!
测试输入: 37 21 75 55 64 19 -1
55 //待查找的关键字为55
预期输出: 37 21 75 55 64 19
4 //末尾换行
开始你的任务吧,祝你成功!
最后通关代码:
/*************************************************************
顺序查找 实现文件
更新于2020年6月23日
**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "search.h"
int Search_Seq(SSTable L, KeyType key)
{/*在静态查找表L中采用顺序查找法,查找其关键字等于key的记录,若找到返回该记录在表中位置,否则返回0*/
// 请在这里补充代码,完成本关任务
/********** Begin *********/
int i = 0 ;
while(i<L.length&&key!=L.r[i].key)
i++;
if(L.r[i].key==key)
return i;
else
return 0;
/*
从第一位开始查找,直至数组结束或遇到第一个key值
退出循环
判断退出原因
找到key -- 返回下标【从0开始】
否则 -- 返回0
*/
/********** End **********/
}
void SSTableInput(SSTable &L) /*输入若干记录的关键字,存放到静态查找表L中*/
{
int i=1; KeyType x;
scanf("%d",&x);
while(x!=-1)
{
L.r[i++].key=x; scanf("%d",&x);
}
L.length=i-1;
}
void SSTableOutput(SSTable L) /*输出静态查找表L中各记录的关键字*/
{
int i;
for(i=1;i<=L.length;i++)
printf("%d ",L.r[i].key);
printf("\n");
}
第2关:折半查找
任务描述
本关任务:实现折半查找。
相关知识
为了完成本关任务,你需要掌握:1.静态查找表的类型定义,2.折半查找。
静态查找表的类型定义
#define MAXSIZE 100
typedef int KeyType; /*关键字类型*/
typedef struct
{
KeyType key;
/*InfoType otherinfo;*/
}RedType; /*记录类型*/
typedef struct
{
RedType r[MAXSIZE+1]; /*r[0]闲置或用作"监视哨"*/
int length;
}SSTable; /*静态查找表的类型定义*/
折半查找
算法思想:先确定待查元素所在范围;然后找中间元素;将中间元素与给定值 x 比较:若相等,则查找成功;若 x 小,则缩小至左半区;若 x 大,则缩小至右半区;直到相等或范围的下界﹥上界为止。 适用:有序的顺序表。
编程要求
在右侧编辑器中补充代码,完成Search_Bin
函数,以实现顺序查找。具体要求如下:
* Search_Bin:在递增或递减有序的表中查找其关键字等于key的记录,若找到返回该记录在表中位置,否则返回0。
测试说明
可在右侧文件夹中查看step2/Main.cpp
文件,以便于你的操作。
平台会对你编写的代码进行测试。
输入说明: 第一行按递增或递减的顺序输入若干记录的关键字,以-1标志结束。 第二行输入待查找的关键字。 输出说明: 第一行输出静态查找表L中各记录的关键字。 第二行输出查找的结果,如果找到,则输出该记录在表中的位置,否则输出not find!
测试输入: 19 21 37 55 64 75 -1
55 //待查找的关键字为55
预期输出: 19 21 37 55 64 75
4 //末尾换行
开始你的任务吧,祝你成功!
最后通关代码:
/*************************************************************
折半查找 实现文件
更新于2020年6月17日
**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "search.h"
int Search_Bin(SSTable L, KeyType key)/*在递增有序的顺序表L中折半查找其关键字等于key的记录*/
{
// 请在这里补充代码,完成本关任务
/********** Begin *********/
int mid, low = 0,high = L.length;
//设定初始查找范围,注意此处high初始值为L.length而不是L.length-1
while(low<=high)
{
mid= (high + low)/2;
if(key == L.r[mid].key)//找到返回key值下标
return mid;
if(key < L.r[mid].key)//中间值大于待查值--范围前缩
high = mid - 1;
if(key > L.r[mid].key)//中间值小于待查值--范围后缩
low = mid + 1;
}
return 0;//找不到要返回0 -- not find!
/********** End **********/
}
void SSTableInput(SSTable &L) /*输入若干记录的关键字,存放到静态查找表L中*/
{
int i=1; KeyType x;
scanf("%d",&x);
while(x!=-1)
{
L.r[i++].key=x; scanf("%d",&x);
}
L.length=i-1;
}
void SSTableOutput(SSTable L) /*输出静态查找表L中各记录的关键字*/
{
int i;
for(i=1;i<=L.length;i++)
printf("%d ",L.r[i].key);
printf("\n");
}
第3关:二叉排序树的查找
任务描述
本关任务:实现二叉排序树的查找。
相关知识
为了完成本关任务,你需要掌握:1.二叉排序树的定义,2.二叉排序树的查找,3.动态查找表的二叉链表存储表示。
二叉排序树的定义
二叉排序树或者是空树,或者是满足如下性质的二叉树: ● 如果其左子树非空,则左子树上所有结点的值均小于根结点的值; ● 如果其右子树非空,则右子树上所有结点的值均大于根结点的值; ● 左子树和右子树本身又各是一棵二叉排序树。
二叉排序树的查找
在二叉排序树T中查找其关键字等于key的记录结点: ● 若 key 等于根记录结点的关键字,则表示找到,查找成功; ● 若 key 小于根记录结点的关键字,则在左子树上继续查找; ● 若 key 大于根记录结点的关键字,则在右子树上继续查找; ● 若沿着某条路径碰到一个叶子结点还未找到,则查找失败。
动态查找表的二叉链表存储表示
#define MAXSIZE 100
typedef int KeyType; /*关键字类型*/
typedef struct
{
KeyType key;
/*InfoType otherinfo;*/
}RedType; /*记录类型*/
typedef struct BiTNode
{
RedType data;
struct BiTNode *lchild,*rchild;
}BiTNode, *BiTree; /*动态查找表的二叉链表存储表示*/
编程要求
在右侧编辑器中补充代码,完成Search_BST
函数,以实现二叉排序树的查找。具体要求如下:
* Search_BST:在二叉排序树上查找其关键字等于key的记录结点。若找到返回该结点指针,parent指向其双亲;否则返回空指针,parent指向访问路径上最后一个结点。
测试说明
可在右侧文件夹中查看step3/Main.cpp
文件,以便于你的操作。
平台会对你编写的代码进行测试。
输入说明: 第一行输入若干记录的关键字,以-1标志结束。 第二行输入待查找的关键字。 输出说明: 第一行输出二叉排序树先序遍历的结果。 第二行输出二叉排序树中序遍历的结果。 第三行输出查找的结果,如果找到,则输出该结点的双亲结点的关键字,否则输出not find!
测试输入: 19 14 66 21 83 27 55 13 10 50 -1
55 //待查找的关键字为55
预期输出: PreOrder:19 14 13 10 66 21 27 55 50 83 //先序遍历的结果
InOrder:10 13 14 19 21 27 50 55 66 83 //中序遍历的结果
27 //末尾换行
开始你的任务吧,祝你成功!
最后通关代码:
/*************************************************************
二叉排序树的查找 实现文件
更新于2020年7月5日
**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "search.h"
BiTree Search_BST(BiTree T, KeyType key, BiTNode **parent)
{/*在二叉排序树T上查找其关键字等于key的记录结点。若找到返回该结点指针,parent指向其双亲;否则返回空指针,parent指向访问路径上最后一个结点。*/
// 请在这里补充代码,完成本关任务
/********** Begin *********/
///parent 指向一个存放树根地址的指针
/*
if(T!=NULL)
{
if(key==T->data.key)
return T;
*parent = T;//改变parent true
//parent = &T;error
if(key<T->data.key)
return Search_BST(T->lchild, key,parent);
if(key>T->data.key)
return Search_BST(T->rchild, key,parent);
}
else
return NULL;
*/
if(!T||key==T->data.key)
return T;
/*
由 !T 进入if分支
说明T为空,此时直接返回T也可达到返回空的效果
*/
else
{
*parent = T;
if(key<T->data.key)
return Search_BST(T->lchild, key,parent);
else
return Search_BST(T->rchild, key,parent);
}
/********** End **********/
}
void Insert_BST(BiTree *T, RedType r)/*若二叉排序树T中没有关键字为r.key的记录,则插入*/
{
BiTNode *p,*q,*parent;
parent=NULL;
p=Search_BST(*T,r.key,&parent); /*查找*/
if(p) printf("BST中有结点r,无需插入\n");
else
{
p=parent;
q=(BiTNode *)malloc(sizeof(BiTNode)); q->data=r; q->lchild=q->rchild=NULL;
if(*T==NULL) *T=q; /*若T为空,则q为新的根*/
else if(r.key<p->data.key) p->lchild=q;
else p->rchild=q;
}
}
BiTree Create_BST( ) /*二叉排序树的构造*/
{/*输入若干记录的关键字(以-1标志结束),生成一棵BST,采用二叉链表存储,返回其根指针T*/
BiTree T; RedType r;
T=NULL; /*建空树*/
scanf("%d",&r.key);
while(r.key!=-1)
{
Insert_BST(&T, r);
scanf("%d",&r.key);
}
return T;
}
void PreOrder(BiTree bt) /*先序遍历*/
{
if(bt)
{
printf("%d ",bt->data.key);
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
void InOrder(BiTree bt) /*中序遍历*/
{
if(bt)
{
InOrder(bt->lchild);
printf("%d ",bt->data.key);
InOrder(bt->rchild);
}
}
第4关:采用除留余数法加线性探测法建立哈希表
任务描述
本关任务:采用除留余数法加线性探测法建立哈希表。
相关知识
为了完成本关任务,你需要掌握: 1.哈希表结构; 2.根据处理冲突的不同方法,设计不同的哈希表结构并进行初始化。
哈希表的基本概念
顺序表、树表的缺点: 查找所需时间与比较次数有关,能不能不比较,一次存取就能得到所查元素?
哈希表(Hash Table)又称散列表,是除顺序存储结构、链式存储结构和索引表存储结构之外的又一种存储结构。
散列表的基本思想:根据记录选择一个关键字key,同时为每个key计算一个散列码Hash(key),将记录存储位置与散列码关联起来。
需要解决两个问题:
- 如何构造散列函数?对给定的一组关键字,使其计算简单,并且分布均匀,即减少冲突。
- 如何处理冲突?因为不可避免。
构造哈希函数的方法
构造哈希函数的目标:使得到的哈希地址尽可能均匀地分布在m个连续内存单元地址上,同时使计算过程尽可能简单以达到尽可能高的时间效率。 根据关键字的结构和分布的不同,有多种构造哈希函数的方法,例如除留余数法。
除留余数法
思想: 以关键字被某个数p除后所得余数作为散列地址 公式: H(key) = key % p 关键:p 如何选取? 一般选择小于表长的最大质数
举例:给定存储单元地址为1,000,000至1,000,027,即表长度为28,如何计算关键字172,148的散列地址?
H(key)= 172148 % 23 + 1,000,000 = 16+1,000,000 = 1,000,016
哈希冲突解决方法
开放地址法 思想: 当冲突发生时,形成一个探查序列;沿此序列逐个地址探查,直到找到一个空位置(开放的地址),将发生冲突的记录放到该地址中 公式: Hi=(H(key)+di)%m,i=1,2,……k(k<=m-1)
其中:m是散列表表长,di是增量序列 线性探测法:di=1,2,3,……m-1 二次探测法:di=1²,-1²,2²,-2²,3²,……±k²(k<=m/2) 伪随机探测法:di=伪随机数序列
【例】假设哈希表长度m=13,采用除留余数法加线性探测法建立如下关键字集合的哈希表:(16,74,60,43,54,90,46,31,29,88,77),除留余数法的哈希函数为:h(k)=k mod 13,散列表的存储空间是一个下标从0开始的一维数组。
构造的哈希表如下:
h(16)=3 ha[3]=16,共1次探测 h(74)=9 ha[9]=74,共1次探测 h(60)=8 ha[8]=60,共1次探测 h(43)=4 ha[4]=43,共1次探测 h(54)=2 ha[2]=54,共1次探测 h(90)=12 ha[12]=90,共1次探测 h(46)=7 ha[7]=46,共1次探测 h(31)=5 ha[5]=31,共1次探测 h(29)=3 有冲突 d0=3,d1=(3+1) % 13=4 仍有冲突 d2=(3+2) % 13=5 仍有冲突 d3=(3+3) % 13=6 ha[6]=29,共4次探测 h(88)=10 ha[10]=88,共1次探测 h(77)=12 有冲突 d0=12,d1=(12+1) % 13=0 ha[0]=77,共2次探测
查找成功时: ASL=(1×9+2×1+4×1) /11 = 15/11 查找失败时: ASL=(2+1+10+9+8+7+6+5+4+3+2+1+3) /13 = 61/13
开放定址哈希表的存储结构
#define NULL_KEY -1 // ,初始化的哈希表中元素均为-1,-1为无记录标志
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
KeyType key;
};
struct HashTable
{
ElemType *elem; // 数据元素存储基址,动态分配数组
int count; // 当前数据元素个数
int sizeindex; // sizeindex为哈希表表长
};
哈希函数的基本操作函数
// 哈希函数的基本操作函数
void InitHashTable(HashTable &H); // 构造一个空的哈希表
void DestroyHashTable(HashTable &H); // 销毁哈希表H
unsigned Hash(KeyType K); // 哈希函数
void collision(int &p,int d); // 线性探测再散列
int SearchHash(HashTable H,KeyType K,int &p,int &c); // 在开放定址哈希表H中查找关键码为K的元素
int InsertHash(HashTable &H,ElemType e); // 查找不成功时插入数据元素e到开放定址哈希表H中
void TraverseHash(HashTable H,void(*Vi)(int));// 遍历哈希表
编程要求
根据提示,在右侧编辑器补充代码,采用除留余数法加线性探测法建立哈希表,编写函数实现哈希表的部分基本运算:
unsigned Hash(KeyType K); // 哈希函数
void collision(int &p,int d); // 线性探测再散列
int SearchHash(HashTable H,KeyType K,int &p,int &c); // 在开放定址哈希表H中查找关键码为K的元素
int InsertHash(HashTable &H,ElemType e); // 查找不成功时插入数据元素e到开放定址哈希表H中
测试说明
平台会对你编写的代码进行测试:
测试输入: 13
13
11
16 74 60 43 54 90 46 31 29 88 77
输入说明: 第一行输入13,表示输入哈希表的表长; 第二行输入13,表示除留余数法的模; 第三行输入11,表示输入数据的个数; 第四行输入11个整数,表示输入具体的数据。
预期输出: 哈希地址0~12
0 1 2 3 4 5 6 7 8 9 10 11 12
77 -1 54 16 43 31 29 46 60 74 88 -1 90
输出说明: 第一行输出哈希表的地址范围 第二行输出哈希表的下标 第三行输出哈希表中的数据
开始你的任务吧,祝你成功!
最后通关代码:
#include"stdio.h"
#include"stdlib.h"
#define NULL_KEY -1 // -1为无记录标志
#define SUCCESS 1
#define UNSUCCESS 0
#define DUPLICATE -1
#define N 100 // 数据元素个数
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
KeyType key;
};
struct HashTable
{
ElemType *elem; // 数据元素存储基址,动态分配数组
int count; // 当前数据元素个数
int sizeindex; // sizeindex为哈希表表长
};
// 哈希函数的基本操作函数
void InitHashTable(HashTable &H); // 构造一个空的哈希表
void DestroyHashTable(HashTable &H); // 哈希表H存在。操作结果:销毁哈希表H
unsigned Hash(KeyType K); // 哈希函数
void collision(int &p,int d); // 线性探测再散列
int SearchHash(HashTable H,KeyType K,int &p,int &c); // 在开放定址哈希表H中查找关键码为K的元素
int InsertHash(HashTable &H,ElemType e); // 查找不成功时插入数据元素e到开放定址哈希表H中
void TraverseHash(HashTable H,void(*Vi)(int));
void print(int p);
int m=0; // 全局变量, H(key) = key % m
int main()
{
ElemType r[N]={0};
HashTable h;
int i,p,n;
int j;
KeyType k;
InitHashTable(h);
scanf("%d",&m); // H(key) = key % m
scanf("%d",&n); // 数据的个数
for(i=0;i<n;i++)
{
scanf("%d",&r[i].key);
}
for(i=0;i<n;i++)
{ // 插入前N-1个记录
j=InsertHash(h,r[i]);
}
//printf("按哈希地址的顺序遍历哈希表:\n");
TraverseHash(h,print);
DestroyHashTable(h);
}
// 哈希函数的基本操作函数
void InitHashTable(HashTable &H)
{ // 操作结果:构造一个空的哈希表
int i;
H.count=0; // 当前元素个数为0
scanf("%d",&H.sizeindex ); // 哈希表存储容量
H.elem=(ElemType*)malloc(H.sizeindex*sizeof(ElemType));
for(i=0;i<H.sizeindex;i++)
H.elem[i].key=NULL_KEY; // 未填记录的标志
}
void DestroyHashTable(HashTable &H)
{ // 初始条件:哈希表H存在。操作结果:销毁哈希表H
free(H.elem);
H.elem=NULL;
H.count=0;
H.sizeindex=0;
}
unsigned Hash(KeyType K)
{ // 一个简单的哈希函数(m为全局变量)
/********** Begin **********/
return K%m;
/********** End **********/
}
void collision(int &p,int d) // 线性探测再散列
{ // 开放定址法处理冲突
/********** Begin **********/
p=(p+1)%m;
/********** End **********/
}
int SearchHash(HashTable H,KeyType K,int &p,int &c)
{ // 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据
// 元素在表中位置,并返回SUCCESS;否则,以p指示插入位置,并返回UNSUCCESS
// c用以计冲突次数,其初值置零,供建表插入时参考。
/********** Begin **********/
p=Hash(K);
while( H.elem[p].key!=NULL_KEY && K!= H.elem[p].key )
{
c++;
if(c<H.sizeindex)
collision(p,c);
else
break;
}
if (K == H.elem[p].key)
return SUCCESS;
else
return UNSUCCESS;
/********** End **********/
}
int InsertHash(HashTable &H,ElemType e)
{ // 查找不成功时插入数据元素e到开放定址哈希表H中,并返回OK;
// 若冲突次数过大,则重建哈希表,算法9.18
/********** Begin **********/
int c,p;
c=0;
if(SearchHash(H,e.key,p,c))
return DUPLICATE;
else if(c<H.sizeindex)
{
H.elem[p]=e;
++H.count;
return SUCCESS;
}
else
return UNSUCCESS;
/********** End **********/
}
void TraverseHash(HashTable H,void(*Vi)(int))
{ // 按哈希地址的顺序遍历哈希表
printf("哈希地址0~%d\n",H.sizeindex-1);
for(int i=0;i<H.sizeindex;i++)
// if(H.elem[i].key!=NULL_KEY) // 有数据
Vi(i);
printf("\n");
for(int i=0;i<H.sizeindex;i++)
// if(H.elem[i].key!=NULL_KEY) // 有数据
Vi(H.elem[i].key);
printf("\n");
}
void print(int p)
{
printf("%d\t",p);
}
第5关:在采用除留余数法加线性探测法建立的哈希表中查找数据
任务描述
本关任务:在采用除留余数法加线性探测法建立的哈希表中查找数据,并将查找过程输出。
已知哈希表长度m=13,采用除留余数法加线性探测法建立如下关键字集合的哈希表:(16,74,60,43,54,90,46,31,29,88,77),除留余数法的哈希函数为:h(k)=k mod 13,散列表的存储空间是一个下标从0开始的一维数组。
构造的哈希表如下:
h(16)=3 ha[3]=16,共1次探测 h(74)=9 ha[9]=74,共1次探测 h(60)=8 ha[8]=60,共1次探测 h(43)=4 ha[4]=43,共1次探测 h(54)=2 ha[2]=54,共1次探测 h(90)=12 ha[12]=90,共1次探测 h(46)=7 ha[7]=46,共1次探测 h(31)=5 ha[5]=31,共1次探测 h(29)=3 有冲突 d0=3,d1=(3+1) % 13=4 仍有冲突 d2=(3+2) % 13=5 仍有冲突 d3=(3+3) % 13=6 ha[6]=29,共4次探测 h(88)=10 ha[10]=88,共1次探测 h(77)=12 有冲突 d0=12,d1=(12+1) % 13=0 ha[0]=77,共2次探测
查找成功时: ASL=(1×9+2×1+4×1) /11 = 15/11 查找失败时: ASL=(2+1+10+9+8+7+6+5+4+3+2+1+3) /13 = 61/13
编程要求
根据提示,在右侧编辑器补充代码,采用除留余数法加线性探测法建立哈希表,编写函数实现在哈希表在查找数据的过程:
int Find(HashTable H,KeyType K,int &p); // 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据元素在表中位置,并返回SUCCESS;否则,返回UNSUCCESS
测试说明
平台会对你编写的代码进行测试:
测试输入: 13
13
11
16 74 60 43 54 90 46 31 29 88 77
29
输入说明: 第一行输入13,表示输入哈希表的表长 第二行输入13,表示除留余数法的模 第三行输入11,表示输入数据的个数 第四行输入11个整数,表示输入具体的数据 第五行输入要查找的数据
预期输出: 哈希地址0~12
0 1 2 3 4 5 6 7 8 9 10 11 12
77 -1 54 16 43 31 29 46 60 74 88 -1 90
哈希地址为3,第1次冲突
哈希地址为4,第2次冲突
哈希地址为5,第3次冲突
哈希地址为6,第4次查找成功
查找成功:29
输出说明: 第一行输出哈希表的数据范围 第二行输出哈希表的下标 第三行输出哈希表中的数据 第四行起输出查找数据的过程
开始你的任务吧,祝你成功!
最后通关代码:
#include"stdio.h"
#include"stdlib.h"
#define NULL_KEY -1 // -1为无记录标志
#define SUCCESS 1
#define UNSUCCESS 0
#define DUPLICATE -1
#define N 100 // 数据元素个数
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
KeyType key;
};
struct HashTable
{
ElemType *elem; // 数据元素存储基址,动态分配数组
int count; // 当前数据元素个数
int sizeindex; // sizeindex为哈希表表长
};
// 哈希函数的基本操作函数
void InitHashTable(HashTable &H); // 构造一个空的哈希表
void DestroyHashTable(HashTable &H); // 哈希表H存在。操作结果:销毁哈希表H
unsigned Hash(KeyType K); // 哈希函数
void collision(int &p,int d); // 线性探测再散列
int SearchHash(HashTable H,KeyType K,int &p,int &c); // 在开放定址哈希表H中查找关键码为K的元素
int InsertHash(HashTable &H,ElemType e); // 查找不成功时插入数据元素e到开放定址哈希表H中
void TraverseHash(HashTable H,void(*Vi)(int));
void print(int p);
int Find(HashTable H,KeyType K,int &p);
int m=0; // 全局变量, H(key) = key % m
int main()
{
ElemType r[N]={0};
HashTable h;
int i,p,n;
int j;
KeyType k;
InitHashTable(h);
scanf("%d",&m); // H(key) = key % m
scanf("%d",&n); // 数据的个数
for(i=0;i<n;i++)
{
scanf("%d",&r[i].key);
}
for(i=0;i<n;i++)
{ // 插入前N-1个记录
j=InsertHash(h,r[i]);
}
//printf("按哈希地址的顺序遍历哈希表:\n");
TraverseHash(h,print);
//printf("请输入待查找记录的关键字: ");
scanf("%d",&k);
j=Find(h,k,p);
if(j==SUCCESS)
{
printf("查找成功:");
print(h.elem[p].key);
}
else
printf("查找失败\n");
DestroyHashTable(h);
}
// 哈希函数的基本操作函数
void InitHashTable(HashTable &H)
{ // 操作结果:构造一个空的哈希表
int i;
H.count=0; // 当前元素个数为0
scanf("%d",&H.sizeindex ); // 哈希表存储容量
H.elem=(ElemType*)malloc(H.sizeindex*sizeof(ElemType));
for(i=0;i<H.sizeindex;i++)
H.elem[i].key=NULL_KEY; // 未填记录的标志
}
void DestroyHashTable(HashTable &H)
{ // 初始条件:哈希表H存在。操作结果:销毁哈希表H
free(H.elem);
H.elem=NULL;
H.count=0;
H.sizeindex=0;
}
unsigned Hash(KeyType K)
{ // 一个简单的哈希函数(m为全局变量)
return K%m;
}
void collision(int &p,int d) // 线性探测再散列
{ // 开放定址法处理冲突
p=(p+1)%m;
}
int SearchHash(HashTable H,KeyType K,int &p,int &c)
{ // 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据
// 元素在表中位置,并返回SUCCESS;否则,以p指示插入位置,并返回UNSUCCESS
// c用以计冲突次数,其初值置零,供建表插入时参考。
p=Hash(K); // 求得哈希地址
while( H.elem[p].key!=NULL_KEY && K!= H.elem[p].key )
{ // 该位置中填有记录.并且关键字不相等
c++;
if(c<H.sizeindex)
collision(p,c); // 求得下一探查地址p
else
break;
}
if (K == H.elem[p].key)
return SUCCESS; // 查找成功,p返回待查数据元素位置
else
return UNSUCCESS; // 查找不成功(H.elem[p].key==NULL_KEY),p返回的是插入位置
}
int InsertHash(HashTable &H,ElemType e)
{ // 查找不成功时插入数据元素e到开放定址哈希表H中,并返回OK;
// 若冲突次数过大,则重建哈希表
int c,p;
c=0;
if(SearchHash(H,e.key,p,c)) // 表中已有与e有相同关键字的元素
return DUPLICATE;
else if(c<H.sizeindex) // 冲突次数c未达到上限,(c的阀值可调)
{ // 插入e
H.elem[p]=e;
++H.count;
return SUCCESS;
}
else
return UNSUCCESS;
}
void TraverseHash(HashTable H,void(*Vi)(int))
{ // 按哈希地址的顺序遍历哈希表
printf("哈希地址0~%d\n",H.sizeindex-1);
for(int i=0;i<H.sizeindex;i++)
// if(H.elem[i].key!=NULL_KEY) // 有数据
Vi(i);
printf("\n");
for(int i=0;i<H.sizeindex;i++)
// if(H.elem[i].key!=NULL_KEY) // 有数据
Vi(H.elem[i].key);
printf("\n");
}
void print(int p)
{
printf("%d\t",p);
}
int Find(HashTable H,KeyType K,int &p)
{ // 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据
// 元素在表中位置,并返回SUCCESS;否则,返回UNSUCCESS
/********** Begin **********/
p=Hash(K);
int c=0;
while(H.elem[p].key!=NULL_KEY&&H.elem[p].key!=K)
{
c++;
printf("哈希地址为%d,第%d次冲突\n",p,c);
if(c<H.sizeindex)
{
collision(p,c);
}
}
c++;
if(H.elem[p].key==K)
{
printf("哈希地址为%d,第%d次查找成功\n",p,c);
return SUCCESS;
}
else
{
printf("哈希地址为%d,第%d次查找为空\n",p,c);
return UNSUCCESS;
}
/********** End **********/
}
任务描述
本关讨论散列存储,哈希函数使用除留余数法,冲突解决方法采用独立链表地址法。
相关知识
为了完成本关任务,你需要掌握: 1.哈希表结构; 2.根据链地址法处理冲突,设计哈希表。
哈希表的基本概念
哈希表不可避免冲突现象:对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。 具有相同函数值的关键字对该哈希函数来说称为同义词。
因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表。
构造哈希函数的方法
构造哈希函数的目标:使得到的哈希地址尽可能均匀地分布在m个连续内存单元地址上,同时使计算过程尽可能简单以达到尽可能高的时间效率。 根据关键字的结构和分布的不同,有多种构造哈希函数的方法,例如除留余数法。
除留余数法
思想: 以关键字被某个数p除后所得余数作为散列地址 公式: H(key) = key % p 关键:p 如何选取? 一般选择小于表长的最大质数
举例:给定存储单元地址为1,000,000至1,000,027,即表长度为28,如何计算关键字172,148的散列地址?
H(key)= 172148 % 23 + 1,000,000 = 16+1,000,000 = 1,000,016
哈希冲突解决方法
本关讨论采用独立链地址法解决冲突问题。
将所有关键字为"同义词"的记录链接在一个线性链表中。此时的散列表以"指针数组"的形式出现,数组内各个分量存储相应散列地址的链表的头指针。
举例:已知一组关键字(19,56,23,14,68,82,70,36,91 ), 散列函数为:hash(key)=key%7
,用链地址法处理冲突,构造的哈希表存储结构如下所示:
由 7 个独立链表组成,散列值相同的关键码在同一个链表里,独立链表的头结点组成散列表,一共 7 行,编号 0 , 1 , … , 6 。 哈希表类型定义如下:
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
KeyType key;
};
typedef struct Node //链地址,其实这个指针就是链表
{
ElemType r;
struct Node* next;
}Node;
typedef struct
{
Node** rcd; //(指向指针的指针)存放指针的数组
int size; //哈希表的容量
int count; //当前表中含有的记录个数
}HashTable;
定义如下操作,各操作函数的功能详见下面:
unsigned Hash(KeyType K,int m)
{ // 一个简单的哈希函数
return K%m;
}
int InitHash(HashTable& H, int size)
{
H.rcd = (Node**)malloc(size*sizeof(Node*));
if (H.rcd != NULL)
{
for (int i = 0; i < size; i++)
{
H.rcd[i] = NULL;
}
H.size = size;
H.count = 0;
return 1;
}
else
{
return 0;
}
}
void HashTraverse(HashTable H)
{
if (H.rcd != NULL)
{
Node* np, * nq;
for (int i = 0; i < H.size; i++)
{
printf("%d: ", i);
if (H.rcd [i])
{
printf("%d ",H.rcd [i]->r .key);
Node* curr=H.rcd [i]->next;
while (curr) {
printf("-> %d ", curr->r.key);
curr=curr->next;
}
printf("\n");
}
else
printf("-\n");
}
}
}
int DestroyHash(HashTable& H)
{
if (H.rcd != NULL)
{
Node* np, * nt;
for (int i = 0; i < H.size; i++)
{
np = H.rcd[i];
while (np != NULL)
{
nt = np;
np = np->next;
free(nt);
}
}
H.size = 0;
H.count = 0;
return 1;
}
else
{
return 0;
}
}
编程要求
本关的编程任务是补全函数来分别实现哈希表的查找、插入操作。
Node* SearchHash(HashTable H, KeyType key); //查找
int InsertHash(HashTable& H, ElemType e); //插入
测试说明
平台会对你编写的代码进行测试:
样例输入: 7
9
19 56 23 14 68 82 70 36 91
输入说明: 第一行输入7,表示输入哈希表的表长 第二行输入9,表示输入数据的个数 第三行输入9个整数,表示输入具体的数据
样例输出: 采用链地址法得到的哈希表为:
0: 91 -> 70 -> 14 -> 56
1: 36
2: 23
3: -
4: -
5: 82 -> 68 -> 19
6: -
输出说明: 输出采用除留余数法加链地址法建立哈希表,输出
n
个独立链表。
开始你的任务吧,祝你成功
最后通关代码:
#include <stdio.h>
#include <stdlib.h>
#define N 20 // 数据元素个数
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
KeyType key;
};
typedef struct Node { //链地址,其实这个指针就是链表
ElemType r;
struct Node* next;
}Node;
typedef struct {
Node** rcd; //(指向指针的指针)存放指针的数组
int size; //哈希表的容量
int count; //当前表中含有的记录个数
}HashTable;
int InitHash(HashTable& H, int size); //初始化哈希表
int DestroyHash(HashTable& H); //销毁哈希表
Node* SearchHash(HashTable H, KeyType key); //查找
int InsertHash(HashTable& H, ElemType e); //插入
int DeleteHash(HashTable& H, KeyType key, ElemType& e); //删除
void HashTraverse(HashTable H); //遍历
unsigned Hash(KeyType K,int m);
int main()
{
HashTable h;
KeyType k;
ElemType r[N]={0};
int m,n,i,j;
scanf("%d",&m); // H(key) = key % m
scanf("%d",&n); // 数据的个数
InitHash(h,m);
for(i=0;i<n;i++)
{
scanf("%d",&r[i].key);
}
for(i=0;i<n;i++)
{ // 插入前N-1个记录
InsertHash(h, r[i]);
}
printf("采用链地址法得到的哈希表为:\n");
HashTraverse(h);
DestroyHash(h);
}
unsigned Hash(KeyType K,int m)
{ // 一个简单的哈希函数
return K%m;
}
int InitHash(HashTable& H, int size)
{
H.rcd = (Node**)malloc(size*sizeof(Node*));
if (H.rcd != NULL)
{
for (int i = 0; i < size; i++)
{
H.rcd[i] = NULL;
}
H.size = size;
H.count = 0;
return 1;
}
else
{
return 0;
}
}
int DestroyHash(HashTable& H)
{
if (H.rcd != NULL)
{
Node* np, * nt;
for (int i = 0; i < H.size; i++)
{
np = H.rcd[i];
while (np != NULL)
{
nt = np;
np = np->next;
free(nt);
}
}
H.size = 0;
H.count = 0;
return 1;
}
else
{
return 0;
}
}
Node* SearchHash(HashTable H, KeyType key)
{
/*********BEGIN*********/
int p;
Node* np;
p = Hash(key,H.size);
np = H.rcd[p];
while (np)
{
if(key == np->r.key)
return np ;
np = np->next;
}
return NULL;
/**********END**********/
}
int InsertHash(HashTable& H, ElemType e)
{
/*********BEGIN*********/
int p;
Node* np;
np = SearchHash(H, e.key);
if (np == NULL)
{
p = Hash(e.key,H.size);
np = (Node*)malloc(sizeof(Node));
np->r = e;
np->next = H.rcd[p];
H.rcd[p] = np;
H.count++;
return 1;
}
else
{
return 0;
}
/**********END**********/
}
void HashTraverse(HashTable H)
{
if (H.rcd != NULL)
{
Node* np, * nq;
for (int i = 0; i < H.size; i++)
{
printf("%d: ", i);
if (H.rcd [i])
{
printf("%d ",H.rcd [i]->r .key);
Node* curr=H.rcd [i]->next;
while (curr) {
printf("-> %d ", curr->r.key);
curr=curr->next;
}
printf("\n");
}
else
printf("-\n");
}
}
}
第7关:在采用除留余数法加链地址法建立的哈希表中删除数据
任务描述
本关实现在采用除留余数法加链地址法建立的哈希表中删除数据。
相关知识
为了完成本关任务,你需要掌握: 1.哈希表结构; 2.根据链地址法处理冲突,设计哈希表。
哈希表的基本概念
哈希表不可避免冲突现象:对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。 具有相同函数值的关键字对该哈希函数来说称为同义词。
因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表。
构造哈希函数的方法
构造哈希函数的目标:使得到的哈希地址尽可能均匀地分布在m个连续内存单元地址上,同时使计算过程尽可能简单以达到尽可能高的时间效率。 根据关键字的结构和分布的不同,有多种构造哈希函数的方法,例如除留余数法。
除留余数法
思想: 以关键字被某个数p除后所得余数作为散列地址 公式: H(key) = key % p 关键:p 如何选取? 一般选择小于表长的最大质数
举例:给定存储单元地址为1,000,000至1,000,027,即表长度为28,如何计算关键字172,148的散列地址?
H(key)= 172148 % 23 + 1,000,000 = 16+1,000,000 = 1,000,016
哈希冲突解决方法
本关讨论采用独立链地址法解决冲突问题。
将所有关键字为"同义词"的记录链接在一个线性链表中。此时的散列表以"指针数组"的形式出现,数组内各个分量存储相应散列地址的链表的头指针。
举例:已知一组关键字(19,56,23,14,68,82,70,36,91 ), 散列函数为:hash(key)=key%7
,用链地址法处理冲突,构造的哈希表存储结构如下所示:
由 7 个独立链表组成,散列值相同的关键码在同一个链表里,独立链表的头结点组成散列表,一共 7 行,编号 0 , 1 , … , 6 。 哈希表类型定义如下:
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
KeyType key;
};
typedef struct Node //链地址,其实这个指针就是链表
{
ElemType r;
struct Node* next;
}Node;
typedef struct
{
Node** rcd; //(指向指针的指针)存放指针的数组
int size; //哈希表的容量
int count; //当前表中含有的记录个数
}HashTable;
定义如下操作,各操作函数的功能详见下面:
unsigned Hash(KeyType K,int m)
{ // 一个简单的哈希函数
return K%m;
}
int InitHash(HashTable& H, int size)
{
H.rcd = (Node**)malloc(size*sizeof(Node*));
if (H.rcd != NULL)
{
for (int i = 0; i < size; i++)
{
H.rcd[i] = NULL;
}
H.size = size;
H.count = 0;
return 1;
}
else
{
return 0;
}
}
void HashTraverse(HashTable H)
{
if (H.rcd != NULL)
{
Node* np, * nq;
for (int i = 0; i < H.size; i++)
{
printf("%d: ", i);
if (H.rcd [i])
{
printf("%d ",H.rcd [i]->r .key);
Node* curr=H.rcd [i]->next;
while (curr) {
printf("-> %d ", curr->r.key);
curr=curr->next;
}
printf("\n");
}
else
printf("-\n");
}
}
}
int DestroyHash(HashTable& H)
{
if (H.rcd != NULL)
{
Node* np, * nt;
for (int i = 0; i < H.size; i++)
{
np = H.rcd[i];
while (np != NULL)
{
nt = np;
np = np->next;
free(nt);
}
}
H.size = 0;
H.count = 0;
return 1;
}
else
{
return 0;
}
}
编程要求
本关的编程任务是补全函数来分别实现哈希表的删除操作。
int DeleteHash(HashTable& H, KeyType key, ElemType& e); - //删除
测试说明
平台会对你编写的代码进行测试:
样例输入: 7
9
19 56 23 14 68 82 70 36 91
70
输入说明: 第一行输入7,表示输入哈希表的表长 第二行输入9,表示输入数据的个数 第三行输入9个整数,表示输入具体的数据 第四行输入要删除的整数
样例输出: 采用链地址法得到的哈希表为:
0: 91 -> 70 -> 14 -> 56
1: 36
2: 23
3: -
4: -
5: 82 -> 68 -> 19
6: -
删除元素后哈希表为:
0: 91 -> 14 -> 56
1: 36
2: 23
3: -
4: -
5: 82 -> 68 -> 19
6: -
输出说明: 输出采用除留余数法加链地址法建立哈希表,输出
n
个独立链表。 再输出删除元素后的哈希表。
开始你的任务吧,祝你成功
最后通关代码:
#include <stdio.h>
#include <stdlib.h>
#define N 20 // 数据元素个数
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
KeyType key;
};
typedef struct Node { //链地址,其实这个指针就是链表
ElemType r;
struct Node* next;
}Node;
typedef struct {
Node** rcd; //(指向指针的指针)存放指针的数组
int size; //哈希表的容量
int count; //当前表中含有的记录个数
}HashTable;
int InitHash(HashTable& H, int size); //初始化哈希表
int DestroyHash(HashTable& H); //销毁哈希表
Node* SearchHash(HashTable H, KeyType key); //查找
int InsertHash(HashTable& H, ElemType e); //插入
int DeleteHash(HashTable& H, KeyType key, ElemType& e); //删除
void HashTraverse(HashTable H); //遍历
unsigned Hash(KeyType K,int m);
int main()
{
HashTable h;
KeyType k;
ElemType r[N]={0},e;
int m,n,i,j;
scanf("%d",&m); // H(key) = key % m
scanf("%d",&n); // 数据的个数
InitHash(h,m);
for(i=0;i<n;i++)
{
scanf("%d",&r[i].key);
}
for(i=0;i<n;i++)
{ // 插入前N-1个记录
InsertHash(h, r[i]);
}
printf("采用链地址法得到的哈希表为:\n");
HashTraverse(h);
//printf("请输入要删除的整数值:\n");
scanf("%d",&k);
if ( DeleteHash(h, k, e) )
{
printf("删除元素后哈希表为:\n");
HashTraverse(h);
}
else
printf("删除元素失败。\n");
DestroyHash(h);
}
unsigned Hash(KeyType K,int m)
{ // 一个简单的哈希函数
return K%m;
}
int InitHash(HashTable& H, int size)
{
H.rcd = (Node**)malloc(size*sizeof(Node*));
if (H.rcd != NULL)
{
for (int i = 0; i < size; i++)
{
H.rcd[i] = NULL;
}
H.size = size;
H.count = 0;
return 1;
}
else
{
return 0;
}
}
int DestroyHash(HashTable& H)
{
if (H.rcd != NULL)
{
Node* np, * nt;
for (int i = 0; i < H.size; i++)
{
np = H.rcd[i];
while (np != NULL)
{
nt = np;
np = np->next;
free(nt);
}
}
H.size = 0;
H.count = 0;
return 1;
}
else
{
return 0;
}
}
Node* SearchHash(HashTable H, KeyType key)
{
int p;
Node* np;
p = Hash(key,H.size);
np = H.rcd[p];
while (np)
{
if(key == np->r.key)
return np ;
np = np->next;
}
return NULL;
}
int InsertHash(HashTable& H, ElemType e)
{
int p;
Node* np;
np = SearchHash(H, e.key);
if (np == NULL)
{
p = Hash(e.key,H.size);
np = (Node*)malloc(sizeof(Node));
np->r = e;
np->next = H.rcd[p];
H.rcd[p] = np;
H.count++;
return 1;
}
else
{
return 0;
}
}
int DeleteHash(HashTable& H, KeyType key, ElemType& e)
{
/*********BEGIN*********/
Node* n;
n = SearchHash(H, key);
if (n != NULL)
{
int p = Hash(key,H.size);
Node* np, * nq;
np = H.rcd[p];
nq = NULL;
while (np != NULL)
{
if (np->r.key != key)
{
nq = np;
np = np->next;
}
else
{
e = np->r;
if (nq == NULL)
{
H.rcd[p] = np->next;
}
else
{
nq->next = np->next;
}
break;
}
}
H.count--;
free(np);
return 1;
}
else
{
return 0;
}
/**********END**********/
}
void HashTraverse(HashTable H)
{
if (H.rcd != NULL)
{
Node* np, * nq;
for (int i = 0; i < H.size; i++)
{
printf("%d: ", i);
if (H.rcd [i])
{
printf("%d ",H.rcd [i]->r .key);
Node* curr=H.rcd [i]->next;
while (curr) {
printf("-> %d ", curr->r.key);
curr=curr->next;
}
printf("\n");
}
else
printf("-\n");
}
}
}