查找
查找又称为检索,是指在某种数据结构中找出满足给定条件的结点,若找到满足给定值的元素,则查找成功,反之则失败
一、基本概念
1、查找表
查找表是由同一类型的数据元素(或记录)构成的集合。由于"集合”中的数据元素之间存在着松散的关系,因此查找表是一种应用灵便的结构。如图
2、对查找表进行的操作
◆查找某个特定的数据元素是否存在;
◆检索某个特定数据元素的属性;
◆在查找表中插入一个数据元素;
◆在查找表中删除一个数据元素。
3、静态查找( Static Search )
在查找过程中仅查找某个特定元素是否存在或它的属性,称为静态查找。
4.动态查找( Dynamic Search )
若在查找过程中可以将查找表中不存在的数据元素插入,或者从查找表中删除某个数据元素,则称为动态查找。后面介绍的二叉排序树查找就是动态查找。
5、关键字(Key )
是数据元素的某个数据项的值,用它可以标识查找表中的一个或组数据元素。
6、主关键字( Primary Key )
如果一个关键字可以唯一标识查找表中的一 个数据元素, 则称其为主关键字。
7、次关键字( Secondary Key )
可以标识若干个记录的关键字称为次关键字。
例如,银行账户的账号是主关键字。姓名是次主关键字。当数据元素仅有一个数据项时,数据元素的值就是关键字。
8、查找( Searching )
根据给定的关键字值,在特定的查找表中确定一个其 关键字与给定值相同的数据元素,并返回该数据元素在查找表中的位置。
9、内查找和外查找
若整个查找过程全部在内存进行,则称为内查找;若在查找过程中还需要访问外存,则称为外查找。
10、均查找长度ASL
为确定数据元素在查找表中的位置,需和给定值进行比较的关键字个数的期望值,称为查找算法在查找成功时的平均查找长度,简称为ASL。对于长度为n的查找表,查找成功时的平均查找长度为:其中,Pi为查找表中第i个数据元素的概率,且
为找到表中第i个数据元素时,已经进行过的关键字比较次数,它的取值随查找过程的不同而不同。一般情况下,假设查找每个数据元素的概率相等,即对所有的i来说,Pi=1/n。上述对平均查找长度的讨论主要是基于之
,也就是说,每次查找都是成功的。
例1、问题:查找成功否?
查找一根据给定的某个值, 在查找表中确定一个其关键字等 于给定值的数据元素或(记录)
●若查找表中存在这样一 个记录,则称"查找成功”。
●查找结果给出整个记录的信息,或指示该记录在查找表中的位置;
●否则称”查找不成功”
●查找结果给出"空记录”或"空指针”。
例2、问题:查找过程中我们要研究什么?
查找的方法取决于查找表的结构,即表中数据元素是依何种关系组织在一起的。由于对查找表来说,在集合中查询或检索一个"特定的”数据元素时,若无规律可循,只能对集合中的元素一-加以辨认直至找到为止。而这样的"查询”或"检索"是任何计算机应用系统中使用频度都很高的操作,因此设法提高查找表的查找效率,是本章讨论问题的出发点。为提高查找效率,一个办法就是在构造查找表时,在集合中的数据元素之间人为地加上某种确定的约束关系。研究查找表的各种组织方法及其查找过程的实施。
二、静态查找表
1、顺序查找
(1)顺序查找的主要思想
顺序查找又称为线性查找,是种最简单的查找方法,它的基本思路是:从表的一端开始,用所给定的关键字k依次与顺序表中各记录的关键字key逐个比较,若找到相同的,查找成功;否则查找失败。顺序查找的线性表定义如下:
typedef int datafype;//创建顺序表
typedef struct {
datafype data[MAX];
int key_y[MAX];//分块查找的辅助数组
datafype key; //存放关键子key
int Length;
}SeqList;
SeqList l;
(2)顺序查找的算法
用监视哨来编写算法。主要思路是:顺序表中数据元素存放在数组下标为1到n的位置,而下标为0的位置空出来作为监视哨。在查找之前,先将要查找的关键字存放到数组下标为0的位置,从顺序表的最后一个记录开始,从后至前依次进行给定值与记录关键字的比较,若某记录的关键字与给定值k相同,则查找成功,返回该记录在表中的存储位置;若查找不成功,返回0。
其算法描述如下:
void SeqSerch(SeqList *l){//顺序查找法
SeqList *k;
k=l;
int i;
printf("输入要查找的数值:");
scanf("%d",&k->key);
k->data[0]=k->key;//设置监视哨
i=k->Length;
while(k->data[i]!=k->key){
i--;
}
printf("在顺序表第%d位置找到数值为%d",i,k->data[i]);
}
- 监视哨的作用:
●省去判定循环中下标越界的条件,从而节约比较时间:
●保存查找值的副本,查找时若遇到它,则表示查找不成功。这样在从后向前查找失败时,不必须判断查找表是否检测完,从而达到算法统一。
(3)顺序查找的性能分析
●时间复杂度: (O(n)
查找成功时的平均查找长度,设表中各记录查找概率相等
ASL(n)=(1+2+ … +n)/n +(n+1)/2
●空间复杂度: -个辅助空间——O(1);
例3、记录的查找概率不相等时如何提高查找效率?
查找表存储记录原则——按查找概率高低存储:
●查找概率越高,比较次数越少;
●查找概率越低,比较次数较多。
例4、记录的查找概率无法测定时如何提高查找效率?
方法——按查找概率动态调整记录顺序:
●始终保持记录按非递增有序的次序排列;
●在每个记录中设一不访问频度域;
●每次查找后均将刚查到的记录直接移至表头。
(4)顺序查找的特点
优点:算法简单,逻辑次序无要求,且不同存储结构均适用。
缺点:ASL太长,时间效率太低。
2、折半查找
对于一个顺序表,若其中的所有记录按关键字的某种顺序排列,则称为有序表。采用有序表作为存储结构时,查找算法可以采用效率更高的折半查找法。折半查找又称为二分查找。这种查找方法的前提条件是要求查找表必须是按关键字大小有序排列的顺序表。
(1).二分查找的基本思路
在有序表中,取中间元素作为比较对象,若给定值与中间元素的关键字相等,则查找成功;若给定值小于中间元素的关键字,则在中间元素的左半区继续查找;若给定值大于中间元素的关键字,则在中间元素的右半区继续查找。不断重复上述查找过程,直到查找成功,或所查找的区域无数据元素,查找失败。
(2)折半查找的主要步骤如下。
- 置初始查找范围: low=l, high=n (设置初始区间)。
- 求查找范围中间项: mid=(low+high)/2 (取中间点位置)。
- 将指定的关键字值k与中间项的关键字比较:
◆若相等,查找成功,找到的数据元素为此时mid指向的位置:
◆若k 小于中间项关键字,查找范围的低端数据元素指针low不变,高端数据元素指针high更新为mid- 1;
◆若k大于中间项关键字,查找范围的高端数据元素指针high不变,低端数据元素指针low更新为mid+1。 - 重复步骤(2)、 (3)直到查找成功或查找范围为空( low>high), 即查找失败为止。
- 如果查找成功,返回找到元素的存放位置,即当前的中间项位置指针mid;否则返回查找失败标志。
(3)折半查找的算法
void Search_Bin(SeqList *l,datafype key,int low,int high){//折半查找法(仅使用用有序顺序表)
if(low>high){
printf("没有找到!!");
}
int i;
i=(low+high)/2;
if(key==l->data[i]){
printf("在顺序表第%d位置找到数值为%d",i,l->data[i]);
}
else if(key<l->data[i]){
Search_Bin(l,key,low,i-1);//递归调用前半区
}
else{
Search_Bin(l,key,i+1,high);//递归调用后半区
}
}
(4)折半查找的性能分析
折半查找过程可用一个二叉树来描述,把当前查找区间的中间位置上的记录作为根,左子表和右子表中的记录分别作为根的左子树和右子树。树中每个结点表示一个记录,结点中的值为该记录在表中的位置,则称该二叉树为折半查找的判定树。
平均查找长度ASL (成功时) :
设表长n= 2h-1,则h= log2(n+1) (此时,判定树为深度= h 的满二=叉树),且表中每个记录的查找概率相等: Pi= 1/n。
(5)折半查找的优缺点
折半查找优点:效率比顺序查找高。
折半查找缺点:只适用于有序表,且限于顺序存储结构(对线性链表无效)
3、分块查找
分块查找又称为索引顺序查找,是顺序查找的一种改进,其性能介于顺序 查找和折半查找之间。
(1)分块查找的基本思想
分块查找把查找表分成若干块,每块中的元素存储顺序是任意的,但块与块之间必须按关键字大小有序排列。即前块中的最大关键字小于 (或大于)后块中的最小(最大)关键字值。另外还需要建立一个索引表,索引表中的一项对应线性表的一块, 索引项由关键字域和指针域组成,关键字域存放相应块的块内最大关键字,指针域存放指向本块第一个和最后一个元素的指针(即数组下标值)。索引表按关键字值递增(或递减)顺序排列。
(2)分块查找的步骤
分块查找过程分为两步。
- 确定待查找的元素属于哪一块, 在索引表中采用折半查找其所在的块。由于索引表是递增有序的,所以采用折半查找速度较快。
- 采用顺序法在块内查找要查的元素。由于每块内元素个数少,不会对执行速度有太大的影响。
对于关键字序列{6,12,7,10,9,22,15,20,18,26,28,35,30,38,29,31,47,72,56,61,58}线性表采用分块在找法查找关键字为8的无素,其分块索引和索引秋如图索引表:
(3)分块查找的算法
void CreatIdx(SeqList *l){
printf("\n--分块查找法--\n") ;
InitList(l);
CreateList(l);
int i,k,x,y,s,j,h;
printf("需要建立多少块(1-%d):",MAX);
scanf("%d",&y);
k=l->Length/y;
h=1;y=0;s=j=k;
while(k<=l->Length){//划分索引数组
for(i=1;i<k;i++){
if((l->data[i]<l->data[i+1])&&(i+1<=k)){
x=l->data[i+1];
}
else if((l->data[i]>=l->data[i+1])&&(i+1<=k)){
x=l->data[i];
}
}
l->key_y[y]=x;
h++;
y++;
k=s*h;
}
printf("输入查找的数值:");
scanf("%d",&x);
k=j;
for(i=0;i<y;i++){//对照索引数组查找数值
if(x<=l->key_y[i]){
h=i;
for(s=h*k;s<h*k+2;s++){
if(x==l->data[s+1]){
printf("在顺序表第%d位置找到数值为%d",s+1,x);
}
}
}
}
}
(4)分块查找性能分析
4、算法比较
三、动态查找表
1、二叉排序树的概念
(1)二叉排序树的定义
二叉排序树tee) 又称为二又查找树, 它或者是一棵空树,或者是具有以下性质的二又树:
- 若它的左子树非空,则左子树上所有结点的值均小于根结点的值;
- 若它的右子树非空,则右子树上所有结点的值均大于或等于根结点的值;
- (它的左右子树也分别为二叉排序树。
如图所示为个关键字是整型的二叉排序树,由定义可以得出二叉排序树的一个重要性质:中序遍历一个二叉排序树时可以得到一个递增有序序列。
(2)二叉链表的结构类型
typedef struct Tree{
int key;
struct Tree *lchild,*rchild;
}BTNode;
2、 二叉排序树的基本运算
(1)二叉排序树的基本运算如下:
- 插入结点BSTInsert(bt,k):在二叉排序树中插入关键字为k的结点;
- 建立二叉排序树bt-CreateBSTst,n):由关键字数组建立一棵二叉排序树bt;
- 输出二叉排序树DispBST(bt):采用广义表表示法输出二叉排序树bt;
- 删除结点BSTDelete(bt,k):在二叉排序树bt中删除关键字为k的结点;
- 查找结点BSTSearch(bt,k):在二叉排序树bt中查找关键字为k的结点。
(2)二叉排序树的元素插入
己知一个关键字值为key的结点p,插入的方法如下。 - 若二叉排序树是空树,则key生成为二又排序树的根。
- 若二叉排序树非空,则将key与二叉排序树的根进行比较。
◆如果key的值小于根结点的值,则将key插入左子树。
◆如果key的值大于或等于根结点的值,则将key插入右子树。
算法描述如下:
BTNode *BSTInsert(BTNode *tree,int k){
BTNode *f,*p=tree;//p指针指向二叉排序树的根结点
while(p!=NULL){//p指针不为空时
f=p;//指针f指向指针p所指结点
if(p->key>k){//p结点关键字key大于k
p=p->lchild;//p移向左孩子结点
}else{
p=p->rchild;//p移向右孩子结点
}
}
p=(BTNode *)malloc(sizeof(BTNode));//建立新结点
p->key=k;//k赋值到新结点p的key上
p->lchild=p->rchild=NULL;//左右孩子置空
if(tree==NULL){//二叉为空时
tree=p;//新结点作为terr的根节点
}else if(k<f->key){//k小于关键字key
f->lchild=p;//插入为双亲结点的左孩子
}else{
f->rchild=p;//插入为双亲结点的右孩子
}
return(tree);
}
(3)建立二叉排序树算法
BTNode *CreateBST(){//建立二叉树
int i=0,n,x;
BTNode *tree=NULL;
printf("建立多大的二叉树:");
scanf("%d",&n);
printf("请给二叉树赋值(1~%d):",n);
while(i<n){
scanf("%d",&x);
tree=BSTInsert(tree,x);//调用插入函数
i++;
}
return(tree);
}
(4)显示二叉排序树中各元素算法
void DispBStree(BTNode *tree){//输出二叉树排序树
if(tree!=NULL){//二叉排序树不为空
printf("%d",tree->key);//输出根节点的key
if(tree->lchild!=NULL||tree->rchild!=NULL){//二叉排序树的左子树或右子树不为空
printf("(");
DispBStree(tree->lchild);//递归访问左孩子
if(tree->rchild!=NULL){//右孩子不空时
printf(",");
}
DispBStree(tree->rchild);//递归访问右孩子
printf(")");
}
}
}
(5)二叉排序树的元素删除
从二叉排序树中删除个结点,必须保证删除后所得的二叉树仍然满足二叉排序树的性质不变。首先确定被删除的结点是否在二叉排序树中。若不在,则不做任何操作:否则,我们假设结点f为结点p的双亲,结点q为结点S的双亲,结点s为结点p的直接前驱(即s为p左子树中的权值最大的结点,右孩子的情况类似)。-般有以下3种情况。
- 删除叶子结点。若p为叶子结点,则可直接将其删除。语句为:
f->lchild=NULL:free(p);
- 删除单分支结点。若p结点只有左子树,或只有右子树,则可将删除结点p的左子树或右子树直接改为其双亲结点f的左子树(即将其孩子变成其双亲的孩子)。语句为:
f>Ichild-p>lchild (或f>lchild-p->rchid); free(p);
- 删除双分支结点。若p结点有左、右两个子树。有两种方法。第一,可以选择结点p的左子树中权值最大的结点替换它:第二,也可以选择其右子树中权值最小的结点替换它。这两个结点恰好是二叉排序树进行中序遍历序列结点p的直接前驱和直接后继。这样能保证在删除结点p后二叉排序树依然有序,且替换过程移动的结点数最少。
- 在设计算法的过程中,主要讨论前一一种情况, 即用结点p的左子树中权值最大的结点替换p (用右子树中最小权值元素替换p的算法请读者自行编写)。则有两种替换方法:
①将P的左子树改为F的左子树,将P的右子树改为S的右子树。语句为:f>chil-p->Ichid; s->child- p->rchild; free(p);
②将原P结点的值改为S结点的值,刪除原s结点并将原s的左子树改为Q的右子树。语句为:p->data=s->data; q->rchild=s->lchlid ;free(s);
算法如下:
BTNode *BNTDelete_s(BTNode *tree,int x){ //判断删除函数
if((tree!=NULL)&&x==tree->key){//树不为空,被删除值x等与二叉树排序树上的某个值相等
printf("结点%d删除失败!",x);
} else if((tree!=NULL)&&x<tree->key){//被删除值小于根节点,则访问左子树
BNTDelete_s(tree->lchild,x);//递归访问左子树上的左孩子
}else if((tree!=NULL)&&x>tree->key){//被删除值大于根节点,则访问右子树
BNTDelete_s(tree->rchild,x);//递归访问右子树上的右孩子
}else if(tree==NULL){//全部访问完并且没有与删除值相等的key
printf("结点%d删除成功!",x);
}
return 0;
}
BTNode *BNTDelete(BTNode *tree){//删除结点
int k;
BTNode *p,*f,*s,*q,*l;
p=tree;f=NULL;//令p指针等于tree,f指针置空
printf("\n输入要删除的结点:");
scanf("%d",&k);
while(p){
if(p->key==k){//根节点为要删除值时
break;//跳出循环
}
f=p;//令f指针等于p指针
if(p->key>k){//所要删除的值小于key
p=p->lchild;//访问二叉树的左子树
}else{
p=p->rchild;//访问二叉树的右子树
}
}
if(p==NULL){// 二叉树为空树
printf("没有找到该结点!!");
}
if(p->lchild==NULL){//被删除结点没有左子树为空时
if(f==NULL){//f为空
tree=p->rchild;//
}else if(f->lchild==p){
f->lchild=p->rchild;//右子树替换左子树
}else{
f->rchild=p->rchild;
}
free(p);//删除结点p
s=tree;//s指针指向删除完结点tree
BNTDelete_s(s,k); //调用判断删除函数
}else{
q=p;s=p->lchild;//q指针等于p,s指针等于p指针指向的左子树
while(s->rchild){//左子树的右孩子不为空
q=s;//q指针指向s
s=s->rchild;//访问左子树的右孩子
}
if(q==p){
q->lchild=s->lchild;
}else{
q->rchild=s->lchild;
}
p->key=s->key;
free(s);
l=tree;//l指针指向被删除数值的tree
BNTDelete_s(l,k); //调用判断删除的函数
}
return (tree);//返回tree
}
(6)二叉排序树的查找
●若查找的关键字等于根结点,成功
●否则:若小于根结点,查其左子树;若大于根结点,查其右子树
●在左右子树 上的操作类似
算法如下:
BTNode *SearchBST(BTNode *tree,int x){//查找数值
if(tree==NULL){
printf("没有在二叉树上查找到数值!!");
}else if(x==tree->key&&tree!=NULL){
printf("在二叉树上找到值%d为数值",x);//输入的查找数值等于二叉树上的key值时
}else if(x<tree->key&&tree!=NULL){//输入查找数值小于二叉树根节点的key时
return SearchBST(tree->lchild,x);//递归 访问二叉树的左子树
}else if(x>tree->key&&tree!=NULL){//输入查找数值大于二叉树根结点的key时
return SearchBST(tree->rchild,x);//递归访问二叉树的右子树
}
}
(6)二叉排序树的查找分析
含有n个结点的二=叉排序树的平均查找长度和树的形态有关最好情况:初始序列{45,24,53,12,37,93}
ASL=log 2(n+ 1)- 1;树的深度为: 与折半查找中的判定树相同。(形态比较均衡) : O(Iog2n)最坏情况:初始序列{12,24,37,45,53,93}插入的n个元素从一开始就有序,——变成单支树的形态!此时树的深度为n, ASL= (n+ 1)/ 2查找效率与顺序查找情况相同: O(n)
3、平衡二叉树
(1)平衡叉树的定义
平衡二叉树(balanced binary tree)
■又称AVL树(Adelson-Velskii and Landis)。
■一棵平衡二叉树或者是空树, 或者是具有下列性质的二叉排序树:
①左子树与右子树的高度之差的绝对值小于等于1;
②左子树和右子树也是平衡二=叉排序树。
如图
- 对于一棵有n个结点的AVL树, 其高度保持在O(log2n)数量级,ASL也保持在O(log2n)量级
(2)失衡二叉排序树的分析与调整
当我们在一个平衡二 =叉排序树上插入一个结点时,有可能导致失衡,即出现平衡因子绝对值大于1的结点,如: 2、-2。
如果在一棵AVL树中插入一个新结点后造成失衡,则必须重新调整树的结构,使之恢复平衡。
(3)平衡调整的四种类型:
调整原则:
- 降低高度
- 保持二叉排序树性质
四、哈希表(散列表)查找
1、散列表的若干术语
(1)散列方法(杂凑法)
- 选取某个函数,依该函数按关键字计算元素的存储位置,并按此存放;
- 查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素关键码进行比,确定查找是否成功。
(2)散列函数(杂凑函数):散列方法中使用的转换函数
(3)冲突:不同的关键码映射到同一个散列地址
key1xkey2,但是H(key1)=H(key2)
例5:有6个元素的关键码分别为: (25, 21, 39,9,23,11)。
●选取关键码与元素位置间的函数为H(k)= k mod 7,
●地址编号从0-6。
通过散列函数对6个元素建立散列表:
(4)同义词:具有相同函数值的多个关键字
2、散列函数的构造方法
(1)散列存储
选取某个函数,依该函数按关键字计算元素的存储位置Loc(i)=H(keyi)冲突不同的关键码映射到同一个散列地址key1*key2,但是H(key1)=H(key2)在散列查找方法中,冲突是不可能避免的,只能尽可能减少。
- 构造好的散列函数
(a)所选函数尽可能简单,以便提高转换速度;
(b)所选函数对关键码计算出的地址,应在散列地址集中致均匀分 布,以减少空间浪费。 - 制定一个好的解决冲突的方案
查找时,如果从散列函数计算出的地址中查不到关键码,则应当依据解决冲突的规则,有规律地查询其它相关单元。
(2)直接定址法
Hash(key) = axkey + b (a、 b为常数)优点:以关键码key的某个线性函数值为散列地址,不会产生冲突。缺点:要占用连续地址空间,空间效率低。
例6: {100, 300, 500, 700, 800,900},
散列函数Hash(key)=key/100 (a=1/100, b=0)
(3)除留余数法
Hash(key)= key mod p(p是一一个整数)
关键:如何选取合适的p?
技巧:设表长为m,取p≤m且为质数
例7: {15, 23,27, 38,53,61 ,70},
散列函数Hash(key)=key mod 7
(4)平方取中法
当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。因为关键字求平 方后使得它的中间几位和组成关键字的各位值均有关,从而使哈希地址的分布更均匀,减小了发生冲突的可能性。
(5)数字分析法
如果事先知道关键字集合,并且每个关键字的位数比哈希表的地址码位数多时,从关键字中选出分布较均匀的若干位,构成哈希地址。
(6)分段叠加法
这种方法是按哈希表地址位数将关键字分成位数相等的几部分(最后一部 分可以较短),然后将这几部分相加,舍弃最高进位后的结果就是该关键字的哈希地址。具体方法有折叠法与移位法。
(7)伪随机数法
采用一个伪随机函数做哈希函数,即H(key)=random(key)
3、散列表的冲突处理
(1)开放定址法(开地址法)
基本思想:有冲突时就去寻找下一个 空的散列地址,只要散列表足够大,空的散列地址总能找到,并将数据元素存入。
例如:除留余数法H;=(Hash(key)+di)mod m(di为增量序列)
常用方法:线性探测法(di为1,2,…m-1线性序列)、二次探测法(di为1^2, -1^2, 2^2, -2^2, … q^2二次序列)、伪随机探测法(di为伪随机数序列)
例8 一组关键 字{16,46,32 ,40,70,27 42,36,24.49,64},哈希表长度为15.哈希函数为: H(key)= key%13,使用线性探测法解决冲突,构建相应的哈希表。
解:使用线性探测法解决冲突。将关键字值代入哈希函数求取哈希地址。
H(16)=16%13=3 H(46)= 46%13=7 H(32)=32%13=6 H(40)= 40%13=1 H(70)=70%13=5 H(27)=27%13=1 产生冲突:H1=(H(42)+d)%15=(3+1)%15=4 H(36)=-36%13=10 H(24)= 24%13=11 H(49)-49%13=10 产生冲突:H1=(H(27)+d)%15=(1+1)%15=2 H(42) =42%13=3 H-(H(49)+:)9%15-(10+19%15-11 仍有冲突:H2(H(49)+d2)%15=(10+2)%15=12 H(64)=64%13=12 产生冲突:H1(H(64)+d1)%15=(12+1)%15=13
(2)链地址法(拉链法)
基本思想:相同散列地址的记录链成一单链表
m个散列地址就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。
例9:一组关键字为{19,14,23,1,68,20,84,27,55,11,10,79},散列函数为Hash(key)=key mod 13
链地址法建立散列表步骤
●Step1:取数据元素的关键字key,计算其散列函数值(地址)。若该地址对应的链表为空,则将该元素插入此链表;否则执行Step2解决冲突。
●Step2:根据选择的冲突处理方法,计算关键字key的下一个存储地址。若该地址对应的链表为不为空,则利用链表的前插法或后插法将该元素插入此链表。
链地址法的优点:
●链表上结点空间动态申请,更适合于表长不确定的情况
●非同义词不会冲突,无"聚集"现象
4、散列表的查找
例10:已知一-组关键字(19,14,23;1,68,20,84,27,55.11,10,79)散列函数为: H(key)=key MOD 13,散列表长为m=16,设每个记录的查找概率相等
- 用线性探测再散列处理冲突,即Hi=(H(key)+di) MOD m
- 用链地址法处理冲突
(2)散列表的查找效率分析
使用平均查找长度ASL来衡量查找算法,ASL取决于
●处理冲突的方法
●散列函数
●散列表的装填因子α α=表中填入的记录数/哈希表的长度
α越大,表中记录数越多,说明表装得越满,发生冲突的可能性就越大,查找时比较次数就越多。
ASL与装填因子α有关!既不是严格的O(1),也不是O(n)
ASL≈1+α/2(拉链法)
ASL≈1/2(1+1/(1-α))(线性探测法)
ASL≈-(1/α)ln(1-α)(随机探测法)
结论:
●散列表技术具有很好的平均性能,优于一-些传统的技术
●链地址法优于开地址法
●除留余数法作散列函数优于其它类型函数