8.1
查找表的存储:线性表、索引结构、二叉树和哈希表。
对查找表进行的操作主要有:查找、增加和删除元素。其中最主要的运算是进行查找(检索)。
查找方法评价
(1)查找速度
(2)占用存储空间多少
(3)算法本身复杂程度
(4)平均查找长度ASL(Average Search Length):为确定记录在表中的位置,需和给定值进行比较的关键字的个数的期望值叫查找算法的平均查找长度。
平均查找长度(ASL)
8.2静态查找表
8.2.1顺序查找表
顺序查找
int seqsrch(int r[],int n,int k)
{ int i=n;
r[0]=k;
while(r[i]!=k)
i--;
return i;
}
顺序查找方法的ASL
比较次数:
查找第n个元素: 1 查找第n-1个元素:2
查找第i个元素: n+1-i 查找失败: n+1
8.2.2有序表的查找
折半查找(二分法查找)
int binsrch(int r[],int n,int k)
{
int low,high,mid,found;
low=1;
high=n;
found=0;
while((low<=high)&&(found==0))
{
mid=(low+high)/2;
if(k>r[mid])
low=mid+1;
else
if(k==r[mid])
found=1;
else
high=mid-1;
}
if(found==1)
return mid;
else
return 0;
}
8.2.3索引表上的查找
分块查找
算法实现
(1)用数组存放待查记录,每个数据元素至少含有关键字域
(2)建立索引表,每个索引表结点含有最大关键字域和指向本块第一个结点的指针
二叉排序树
二叉排序树(Binary Sort Tree)或者是一棵空二叉树;或者具有下列性质的二叉树:
A. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
B. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
C. 它的左右子树也分别为二叉排序树。
typedef struct BSTNode{
int key;//结点的关键码字段
Elemtype other;//结点的属性字段
struct BSTNode *lchild,*rchild;//二叉树的左、右指针
}BSTNode,*BSTree;
查找思想
首先将给定的K值与二叉排序树的根结点的关键字进行比较:
(a)若相等: 则查找成功;
(b)给定的K值小于BST的根结点的关键字:继续在该结点的左子树上进行查找;
©给定的K值大于BST的根结点的关键字:继续在该结点的右子树上进行查找。
BSTNode* BST_Serach(BSTNode *T,KeyType key)
{
if(T==NULL)
return NULL;
else
{
if(T->key==key)
return T;
else
if(key<T->key)
return BST_Serach(T->Lchild,key);
else
return BST_Serach(T->Rchild, key);
}
}
二叉排序树的平均查找长度与logn同数量级
二叉排序树的插入
在BST树中插入一个新结点x时,若BST树为空,则令新结点x为插入后BST树的根结点;否则,将结点x的关键字与根结点T的关键字进行比较:
① 若相等: 不需要插入;
② 若x.keykey:结点x插入到T的左子树中;
③ 若x.key>T->key:结点x插入到T的右子树中。
void Insert_BST(BSTNode * &T,int key)
{
BSTNode *x;
x=(BSTNode *)malloc(sizeof(BSTNode));
X->key=key;
x->Lchild=x->Rchild=NULL ;
if(T==NULL)
T=x;
else
{
if(T->key==x->key)
return;//已有结点
else
if(key<T->key)
Insert_BST(T->Lchild,key);
else
Insert_BST(T->Rchild,key);
}
}
利用插入可以建立一个二叉排序树
二叉排序树的删除
1.若 * p是叶子结点,由于删除叶子结点不破坏整棵树的结构,因此,只需要修改其双亲结点的指针。
2. 若 * p只有左子树PL或只有右子树PR,此时,只要令PL或PR的根结点直接代替*p即可。
3. 若 * p的左右子树均不空,在删除 * p之前,中序遍历该二叉树得到的序列,删除 * p之后,为保持其它元素之间的相对位置不变,可以有两种做法。
(1)在p的左子树中找出关键码最大的结点r,将r的右指针指向p的右子女,用p的左子女代替p结点。
(2)按中序(对称序)遍历p的左子树,找到关键码最大的结点r,删除r(用r的左子女代替它),用r结点代替被删除结点p。
void Delete(BSTree &T,KeyType key)
{
BSTree p=T;
BSTree f=NULL;
while(p)
{
if(p->key==key)
break;
f=p;
if(p->key>key)
p=p->lchild;
else
p=p->rchild;
}
if(p==NULL)
return; //没有指定结点
q=p;
if((p->lchild)&&(p->rchild))
{
s=p->lchild;
while(s->rchild)
{
q=s;
s=s->rchild;
}
p->data=s->data;//用s结点代替被删除的p
if(q!=p)//如果q有左孩子
q->rchild=s->lchild;
else//如果q没有左孩子
q->lchild=s->lchild;
delete s;
return;
}
else
if(!p->rchild)
p=p->lchild;
else
p=p->rchild;
if(!f)
T=p;
else
if(f->lchild==q)
f->lchild=p;
else
f->rchild=p;
delete q;
}
最好情况下查找时间为O(logn),与二分查找是同样数量级的,最坏情况下查找时间为O(n)。
平衡二叉树
平衡二叉树或者是空树,或者是满足下列性质的二叉树。(1)左子树和右子树深度之差的绝对值不大于1;(2)左子树和右子树也都是平衡二叉树。
平衡因子(BF) :二叉树上结点的左子树的深度减去其右子树深度称为该结点的平衡因子。平衡二叉树上每个结点的平衡因子只可能是-1、0和1。
平衡二叉排序树:如果一颗二叉树既是二叉排序树又是平衡二叉树。平衡二叉排序树的ASL与logn同数量级。
平衡二叉排序树(AVL树)的平衡化旋转
如果在一颗棵平衡的二叉排序树中插入一个新结点,造成了不平衡。此时必须调整树的结构,使之平衡化。
平衡化旋转:单旋转(左旋和右旋),双旋转(左平衡和右平衡)
每插入一个新结点时,AVL树中相关结点平衡状态会发生变化。在插入一个新结点后,需要从插入位置沿着通向根的路径回溯,检查各结点的平衡因子。如果在某一结点发现高度不平衡,停止回溯。从发生不平衡的结点起,沿刚才回溯的路径取下两层的结点。如果这三个结点处于一条直线上,则采用单旋转进行平衡化。单旋转可按其方向分为左单旋转和右单旋转。如果这三个结点处于一条折线上,则采用双旋转进行平衡化。双旋转分为先左后右和先右后左两类。
B-树
定义:一棵m阶的B-树(平衡多叉树),或为空树,或为满足下列特性的m叉树:
(1)树中每个结点至多有m棵子树;
(2)若根结点不是叶子结点,则至少有两棵子树;
(3)除根之外的所有非终端结点至少有m/2(向上取整)棵子树;
(4)所有的叶子结点都出现在同一层次上,并且不带信息,通常称为失败结点(失败结点并不存在,指向这些结点的指针为空。)
(5)所有的非终端结点最多有m-1个关键字。
B-树的插入
B-树m/2(取上界)-1≤结点中的关键字个数≤m-1,并且整个B-树可以看成全部由关键字组成的树,每次插入一个关键字不是在树中添加一个叶子结点,而是在查找的过程中找到叶子结点所在层的上一层(叶子结点是记录,上一层是关键字最后一层),在某个结点中添加一个关键字,若结点的关键字个数不超过m-1,则插入完成,否则产生结点的分裂。
B-树的删除
假设删除关键字不在最下层,设关键字为Ki,则可以用Ai指向子树的最小关键字或Ai-1指向子树的最大关键字替换Ki,再删去这个关键字即可,而该关键字必定在最下层,所以只需讨论删除最下层结点关键字的情况。
假设删除结点在最下层,删除后仍满足B-树定义则删除结束,否则要进行合并结点的操作,合并可能自下向上层层进行。
(1)满足m/2(取上界)-1≤结点中的关键字个数≤m-1
(2)被删除关键字(50)所在结点中的关键字数目等于m/2(取上界)-1,其相邻的某个兄弟(左或右)结点中的关键字数目大于m/2(取上界)-1,则将其兄弟结点的最小(或最大)关键字上移至双亲结点中,而将双亲结点中小于(大于)且紧靠该上移关键字(61)的关键字(53)下移至被删除关键字(50)所在结点
(3)被删关键字(53)所在的结点和其相邻的兄弟结点中的关键字数目均等于m/2(取上界)-1,在删去关键字(53)后,该结点右兄弟中剩余的关键字和指针加上双亲结点中的关键字一起合并到右兄弟结点中
哈希表
构造散列函数的方法
1.直接地址法
2.(特征位抽取法)数字分析法
3.平方取中法
4.折叠法
5.除留余数法
1直接地址法
取关键字或关键字的某个线性函数值为哈希地址:
H(key)=key 或 H(key)=a×key+b
这类散列函数是一对一的映射,一般不会产生冲突。但是,它要求散列地址空间的大小与关键字集合的大小相同。
2数字分析法
对关键字进行分析,取关键字的若干位或其组合作哈希地址
适于关键字位数比哈希地址位数大,且可能出现的关键字事先知道的情况
3平方取种法
取关键字平方后中间几位作哈希地址
适于不知道全部关键字情况
例:key=4731 (4731)2 = 22382361
h(key)=382
4折叠法
将关键字分割成位数相同的几部分,然后取这几部分的叠加和(舍去进位)做哈希地址
有两种类型
移位相加:将分割后的几部分低位对齐相加
移位折叠相加:从一端沿分割界来回折送,然后对齐相加
5除余法
取关键字被某个不大于哈希表表长m的数p除后所得余数作哈希地址,即H(key)=key%p,p≤m (一般选取p≤m 的最大素数)
m=128,256,512,1024
p=127,251,503,1019
选取哈希函数,考虑以下因素
A. 计算哈希函数所需时间;
B. 关键码的长度;
C. 哈希表的大小;
D. 关键码的分布情况;
E. 记录的查找频率。
哈希冲突的解决方法
1.闭散列方法
在基本区域内形成一个探查序列
Hi = (H(key) + di)%m,
i = 1, 2, …, k (k≤m-1)
其中:m为表长,di为增量序列,可有多种取法:
A. di = 1, 2, 3, …, m-1, 称线性探查序列;
B. di = 1², -1², 2², -2², …, k², -k² (k≤m/2),称为二次探查序列;
C. di = ixh2(key), h2(key)是另一个散列函数,称双散列探查序列;(h2(key)=key%(p’)+1,其中p’
2.开散列法
将所有关键字为同义词的记录存储在一个单链表中,并用一维数组存放头指针
设基本区域长度为m,建立m条链表,将所有关键字为同义词的记录存 储在同一条线性链表中。
已知一组关键字
(19,14,23,1,68,20,84,27,55,11,10,79)
哈希函数为:H(key)=key%13,用链地址法处理冲突
ASL成功=(16+24+3+4)/12=1.75
ASL失败=(17+22+33+5)/13=1.92
如果用线性探测法,结果是
ASL成功=(16+2+3*3+4+9)/12=2.5
ASL失败=(1+13+12+11+10+9+8+7+6+5+4+3+2)/13=7