DS博客作业05--查找
0.PTA得分截图
1.本周学习总结
1.1总结查找内容
查找的性能指标ASL
ASL,是查找算法的查找成功时的平均查找长度的缩写,是为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值。
n:记录的个数
pi:查找第i个记录的概率 ( 通常认为pi =1/n )
ci:找到第i个记录所需的比较次数
顺序查找
基本原理:
对于任意一个序列以及一个给定的元素,将给定元素与序列中元素依次比较,直到找出与给定关键字相同的元素,或者将序列中的元素与其都比较完为止。
代码实现:
int SeqSearch(SeqList R,int n,KeyType k)
{ int i=0;
while (i<n && R[i].key!=k) //从表头往后找
i++;
if (i>=n) //未找到返回0
return 0;
else
return i+1;//找到返回逻辑序号i+1
}
ASL(成功)=1/n*n(n+1)/2=(n+1)/2
ASL(不成功)=n
二分查找
基本原理:
在查找时,我们先将被查找的键和子数组的中间键比较。 如果被查找的键小于中间键,我们就在左子数组中继续查找,如果大于我们就在右子数组中继续查找,否则中间键就是我们要找的键。
代码实现:
int BinSearch(SeqList R,int n,KeyType k)
{ int low=0,high=n-1,mid;
while (low<=high) //当前区间存在元素时循环
{ mid=(low+high)/2;
if (R[mid].key==k)//查找成功
return mid+1;
if (k<R[mid].key)
else
{
low=mid+1;
}
}
return 0;
}
或者使用以下递归算法
int BinSearch1(SeqList R,int low,int high,KeyType k)
{ int mid;
if (low<=high) //查找区间存在一个及以上元素
{ mid=(low+high)/2; //求中间位置
if (R[mid].key==k) //查找成功返回其逻辑序号mid+1
return mid+1;
if (R[mid].key>k) //在R[low..mid-1]中递归查找
BinSearch1(R,low,mid-1,k);
else //在R[mid+1..high]中递归查找
BinSearch1(R,mid+1,high,k); }
}
else
return 0;
}
二分查找成功时的ASL
二叉搜索树
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树:
1.若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
2.若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
3.它的左、右子树也分别为二叉排序树。
上图就是一棵二叉搜索树。
二叉搜索树有个特点:中序遍历二叉搜索树会得到一个递增有序序列。
二叉搜索树的操作
创建
BSTNode *CreatBST(KeyType A[],int n) //返回树根指针
{ BSTNode *bt=NULL; //初始时bt为空树
int i=0;
while (i<n)
{
InsertBST(bt,A[i]); //将A[i]插入二叉排序树T中
i++;
}
return bt; //返回建立的二叉排序树的根指针
}
插入
int InsertBST(BSTree &p,KeyType k)
{ if (p==NULL) //原树为空 {
p=new BSTNode;
p->key=k;
p->lchild=p->rchild=NULL;
return 1;
}
else if (k==p->key) //相同关键字的节点0
return 0;
else if (k<p->key)
return InsertBST(p->lchild,k); //插入到左子树
else
return InsertBST(p->rchild,k); //插入到右子树
}
删除
int DeleteBST(BSTree &bt,KeyType k)
{ if (bt==NULL) return 0; //空树删除失败
else
{ if (k<bt->key) return DeleteBST(bt->lchild,k);//递归在左子树中删除为k的节点
else if (k>bt->key) return DeleteBST(bt->rchild,k); //递归在右子树中删除为k的节点
else
{ Delete(bt); //删除*bt节点
return 1;
}
}
}
AVL树(平衡二叉树)
定义:
1.本身首先是一棵二叉搜索树。
2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。
平衡因子
某结点的左子树与右子树的高度(深度)差即为该结点的平衡因子(BF,Balance Factor)。
平衡二叉树上所有结点的平衡因子只可能是 -1,0 或 1。如果某一结点的平衡因子绝对值大于1则说明此树不是平衡二叉树。
上图中的右图是一棵AVL树。
AVL树不平衡的情况
左子树的左子树插入结点 (左左)
代码实现:
//左左情况旋转(t是失衡结点)
void LL(AVLNode** t)
{
if (t != nullptr)
{
AVLNode* tmpPtr = (*t)->left;
(*t)->left = tmpPtr->right; //t左子树的右子树作为t的左子树
tmpPtr->right = *t;
*t = tmpPtr;
}
}
右子树的右子树插入节点 (右右)
代码实现:
//右右情况旋转
void RR(AVLNode** t)
{
if (t != nullptr)
{
AVLNode* tmpPtr = (*t)->right;
(*t)->right = tmpPtr->left;
tmpPtr->left = *t;
*t = tmpPtr;
}
}
左子树的右子树插入节点 (左右)
代码实现:
//左右情况旋转 (t为失衡结点,新节点位于t的左子树的右子树)
void LR(AVLNode** t)
{
RR(&(*t)->left);
LL(t);
}
右子树的左子树插入节点 (右左)
代码实现:
//右左情况旋转
void RL(AVLNode** t)
{
LL(&(*t)->right);
RR(t);
}
B-树定义
1、根结点至少有两个子女;
2、每个非根节点所包含的关键字个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;
3、除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 k 满足:┌m/2┐ <= k <= m ;
4、所有的叶子结点都位于同一层。
在B-树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶子结点时,该k-1个关键字正好是k个孩子包含的关键字的值域的分划。
B+树定义
(1)每个结点至多有m个子女;
(2)除根结点外,每个结点至少有[m/2]个子女,根结点至少有两个子女;
(3)有k个子女的结点必有k个关键字。
B+树的查找与B树不同,当索引部分某个结点的关键字与所查的关键字相等时,并不停止查找,应继续沿着这个关键字左边的指针向下,一直查到该关键字所在的叶子结点为止
B-树的操作
B-树节点的结构体定义
#define MAXM 10 //定义B-树的最大的阶数
typedef int KeyType; //KeyType为关键字类型
typedef struct node //B-树节点类型定义
{ int keynum; //节点当前拥有的关键字的个数
KeyType key[MAXM]; //[1..keynum]存放关键字,[0]不用
struct node *parent; //双亲节点指针
struct node *ptr[MAXM];//孩子节点指针数组[0..keynum]
} BTNode;
B-树的插入
插入操作是指插入一条记录,即(key, value)的键值对。如果B树中已存在需要插入的键值对,则用需要插入的value替换旧的value。若B树不存在这个key,则一定是在叶子结点中进行插入操作。
1)根据要插入的key的值,找到叶子结点并插入。
2)判断当前结点key的个数是否小于等于m-1,若满足则结束,否则进行第3步。
3)以结点中间的key为中心分裂成左右两部分,然后将这个中间的key插入到父结点中,这个key的左子树指向分裂后的左半部分,这个key的右子支指向分裂后的右半部分,然后将当前结点指向父结点,继续进行第3步。
B-树的删除
删除操作是指,根据key删除记录,如果B树中的记录中不存对应key的记录,则删除失败。
1)如果当前需要删除的key位于非叶子结点上,则用后继key(这里的后继key均指后继记录的意思)覆盖要删除的key,然后在后继key所在的子支中删除该后继key。此时后继key一定位于叶子结点上,这个过程和二叉搜索树删除结点的方式类似。删除这个记录后执行第2步
2)该结点key个数大于等于Math.ceil(m/2)-1,结束删除操作,否则执行第3步。
3)如果兄弟结点key个数大于Math.ceil(m/2)-1,则父结点中的key下移到该结点,兄弟结点中的一个key上移,删除操作结束。
否则,将父结点中的key下移与当前结点及它的兄弟结点中的key合并,形成一个新的结点。原父结点中的key的两个孩子指针就变成了一个孩子指针,指向这个新结点。然后当前结点的指针指向父结点,重复上第2步。
有些结点它可能即有左兄弟,又有右兄弟,那么我们任意选择一个兄弟结点进行操作即可。
哈希表
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希冲突:对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突
装填因子:装填因子α=存储的记录个数/哈希表的大小=n/m
α越小,冲突的可能性就越小; α越大(最大可取1),冲突的可能性就越大
哈希函数构造方法
- 直接定址法
直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。
直接定址法的哈希函数h(k)为:h(k)=k+c - 除留余数法
除留余数法是用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。
哈希函数h(k)为:
h(k)=k mod p (mod为求余运算,p≤m)
p最好是质数(素数)。
3.数字分析法
数字分析法是取数据元素关键字中某些取值较均匀的数字位作为哈希地址的方法。即当关键字的位数很多时,可以通过对关键字的各位进行分析,丢掉分布不均匀的位,作为哈希值。它只适合于所有关键字值已知的情况。通过分析分布情况把关键字取值区间转化为一个较小的关键字取值区间。
冲突处理方法:
开放定址法:冲突时找一个新的空闲的哈希地址
1.线性探测法
线性探查法的数学递推描述公式为:
d0=h(k)
di=(di-1+1) mod m (1≤i≤m-1)
2.平方探测法
平方探查法的数学描述公式为:
d0=h(k)
di=(d0± i2) mod m (1≤i≤m-1)
拉链法
拉链法是把所有的同义词用单链表链接起来的方法。
ASL计算
以上图为例,
成功找到第1层的结点,均需要1次关键字比较,共9个结点;成功找到第2层的结点,均需要2次关键字比较,共2个结点。
所以ASL(成功)=(1×9+2×2)/11=1.182
有1个结点的单链表,不成功查找需要1次关键字比较,共有7个这样的单链表;有2个结点的单链表,不成功查找需要2次关键字比较,共有2个这样的单链表
所以ASL(不成功)=(1×7+2×2)/13=0.846
哈希链
结构体定义
typedef struct HashNode{
int key;
struct HashNode *next;
}HashNode,* HashTable;
HashTable ht[MAX];
建哈希链
void CreateHash(HashTable ht[],int n)
{
建n条带头结点的哈希链,初始化链表;
for i=1 to k:
输入数据data
InsertHash(ht,data)
}
哈希链插入数据
InsertHash(ht,data)
{
计算哈希地址adr=data%P;
查找链ht[adr],存在不插入。
不存在:
data生成数据节点node
头插法插入链ht[adr]
}
哈希链删除数据
DeleteHash(ht,data)
{
计算哈希地址adr=data%P;
若链ht[adr]不为空:
查找链ht[adr],找到删除
}