DS博客作业05-查找
0.PTA得分截图
1.本周学习总结(0-4分)
1.1 总结查找内容
1.1.1 查找的基本概念
ASL:是查找算法的查找成功时的平均查找长度的缩写,是为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值。ASL是查找性能好坏指标,一个算法ASL越小其时间性能越好。
(上图一般指的是静态查找,Pi指的是查找表中第i个记录的概率,Ci指的是比较过的关键字个数)
动态和静态查找:若在查找是对表进行修改操作(插入or删除)则称为动态查找反之则为静态查找。
1.1.2 静态查找
顺序查找
int SeqSearch(SeqList R,int n,int K) //R为顺序表,n为顺序表元素个数,K为待查找元素
{
int i= 0;
while(i<n && R[i].key != k)
{
i++;
}
if(i>=n)
{
return -1;
}
else
{
return i;
}
}
成功ASL:
不成功ASL=n(从头找到尾还没有找到)
二分查找
- 二分查找要求线性表是有序的,由于每次都分成左半区间和右半区间,所以可以用二叉树来描述,有左右子表构成的二叉树分别作为根的左右子树,由此得到的二叉树又称为判定树或比较树。
- 查找的过程图示:
- 二叉判定树:
成功的ASL=
不成功ASL=log(n+1)
算法的时间复杂度O(log2n),虽然时间复杂度不高,但是要求顺序表一定要有序。 - 代码:
int Binsearck(SeqList R,int n,KeyType K)
{
int low = 0;
int high = n-1;
int mid = (low + high)/2;
if(R[mid].key == K)
{
return mid;
}
else if(R[mid].key > K)
{
right = mid-1;
}
else
{
left = mid+1;
}
return -1;
}
1.1.3 动态查找
二叉搜索树
二叉搜索树(又称二叉排序树)性质:
(1)若左子树非空,则左子树上的所有节点关键字都小于根节点关键字
(2)若右子树非空,则右子树上的所有节点关键字都大于根节点关键字
(3)根结点的左右子树本身又是一颗二叉搜索树
(4)补充:中序遍历二叉搜索树得到的中序序列是一个递增有序序列
代码:
结构体定义:
typedef struct node
{ KeyType key; //关键字项
InfoType data; //其他数据域
struct node *lchild,*rchild; //左右孩子指针
} BSTNode,*BSTree;
创建及插入:
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; //返回建立的二叉排序树的根指针
}
BinTree Insert(BinTree BST, ElementType X)
{
if (BST == NULL) //空结点
{
BST = new BSTNode; //生成新结点
BST->Data = X;
BST->Left = BST->Right = NULL;
}
else if (X < BST->Data)
{
BST->Left = Insert(BST->Left, X); //插入左子树
}
else if (X > BST->Data)
{
BST->Right = Insert(BST->Right, X); //插入右子树
}
return BST;
}
删除:
情况1:要删除的结点为叶子结点,直接删去
情况2:要删除的结点只有右子树(或左子树),用右孩子(或左孩子)代替它
情况3:要删除的结点同时存在左右子树,从左子树中选择最大的结点的值替代要删除结点的值,并删除选择到的最大结点(一定没有右子树,情况2)
int DeleteBST(BSTree &bt,KeyType k) //bt的值可能发生变化,所以一定要用引用类型
{
if (bt==NULL) //空树删除失败
{
return 0;
}
else
{
if (k<bt->key) //递归在左子树中删除为k的节点
{
return DeleteBST(bt->lchild,k);
}
else if (k>bt->key) //递归在右子树中删除为k的节点
{
return DeleteBST(bt->rchild,k);
}
else
{
Delete(bt);//删除*bt节点
return 1;
}
}
}
void Delete(BSTree &p)//从二叉排序树中删除*p节点
{
BSTNode *q;
if (p->rchild==NULL)//*p节点没有右子树的情况
{
q=p; p=p->lchild;delete q;
}
else if (p->lchild==NULL)//*p节点没有左子树
{ q=p; p=p->rchild;delete q;
}
else Delete1(p,p->lchild);//*p节点既有左子树又有右子树的情况
}
void Delete1(BSTNode *p,BSTNode *&r)//被删节点:p,p的左子树节点:r
{
BSTNode *q;
if (r->rchild!=NULL)
{
Delete1(p,r->rchild); //递归找最右下节点
}
else //找到了最右下节点*r
{
p->key=r->key;
q=r; r=r->lchild;delete q; //将*r的关键字值赋给*p,用r的左孩子替代r并释放q的空间
}
}
AVL树
AVL树的定义:
若一棵二叉树中每个结点的左右子树的高度差不超过1,则称此二叉树为平衡二叉树。一般情况,平衡二叉树总是二叉搜索树。
从平衡因子的角度来说,一棵二叉树的某个结点的平衡因子(结点左右子树的高度差)绝对值不超过1,则该结点是平衡的,若一棵二叉树的所有节点都是平衡的,则称为平衡二叉树。
AVL树的及4种调整做法。
a. LL型调整:
在A结点的左孩子的左子树上插入结点使得A的平衡因子变为2引起失衡,调整方法如下:
b. RR型调整:
在结点A的右孩子的右子树上插入结点使得A的平衡因子变为2引起失衡,调整方法如下:
c. LR型调整:
在结点A的左孩子的右子树上插入结点使得A的平衡因子变为2引起失衡,调整方法如下:
d. RL型调整:
在结点A的右孩子的左子树上插入结点使得A的平衡因子变为2引起失衡,调整方法如下:
B-树(外查找)
定义:B-树是一种平衡的多路查找树。
满足要求要求:
a. 每个节点至多m个孩子节点(至多有m-1个关键字)
b. 除根节点外,其他节点至少有[m/2]个孩子节点(即至少有[m/2]-1个关键字)
c. 若根节点不是叶子节点,根节点至少两个孩子节点
结点特点:
a. 非根结点:孩子个数最小: m/2, 最大:m
b. 非根结点:关键字个数:最小: m/2 -1 , 最大:m-1
c. 根节点至少2个孩子:2--m
d. 结点中的关键字按升序排列
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-树上顺序查找关键字为k的方法为:将k与根节点中的key[i]进行比较,与二叉搜索树类似,不同的是每个结点确定向下查找的路径是n+1路。
a. 若k=key[i],则查找成功;
b. 若k<key[1] 则沿着指针ptr[0]所指的子树继续查找;
c. 若key[i]<k<key[i+1] 则沿着指针ptr[i]所指的子树继续查找;
d. 若k>key[n] 则沿着指针ptr[n]所指的子树继续查找。 -
插入:
B树的插入分为两步:
a. 用查找算法找出插入结点(一定是某个叶子结点)
b. 判断插入结点是否还有位置,即判断该结点关键字个数是否小于m-1
若结点关键字个数<max,直接插入到合适位置上;
若结点关键字个数=max,把结点分裂成两个,做法是创建一个新结点,把原结点中的关键字和插入的关键字排序后从中间位置([m/2]处)分为左右两部分,左部分放在原结点中,右部分放在新结点中,中间的关键字连同新结
点的存储位置插入到双亲结点中,若双亲结点的关键字个数也超过m-1,则重复此过程,直到根节点,若根节点需要分裂则树高度增加一层。
-
删除:
B树的删除分为两步(与插入类似):
(1)用查找算法找出删除结点
(2)删除结点分为两种情况:一种是叶子结点,一种是非叶子层的结点。
a. 在非叶子结点上删除关键字ki- 从pi子树节点借调最大或最小关键字key代替ki。
- pi子树中删除key
- 若子树节点关键字个数< m/2-1,重复步骤1
- 若删除关键字为叶子结点层,按叶子结点删除操作法
b. 在B树的叶子结点b上删除关键字共有以下3种情况: - 若b结点的关键字个数大于Min,说明删去该关键字后该结点仍满足B树的定义,则可直接删去该关键字。
- 若b结点的关键字个数等于Min,说明删去关键字后该结点将不满足B树的定义。若可以从兄弟结点借。
兄弟结点最小关键字上移双亲结点
双亲结点大于删除关键字的关键字下移删除结点 - 若b结点的关键字个数等Min,兄弟节点关键字个数也等于Min
删除关键字
兄弟节点及删除关键字节点、双亲结点中分割二者关键字合并一个新叶子结点
若双亲结点关键字个数<=Min,重复2
B+树
- B+树是B-树的变形树。
- 满足条件:
⑴有n 棵子树的结点中含有n 个关键字;
⑵所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小按增序链接。
⑶所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。
- 满足条件:
1.1.4 散列查找
哈希表
概念:
- 哈希表(散列表)存储线性表的存储结构:主要适合关键字与存储地址存在某种函数关系的数据
- 哈希函数h(key):把关键字为ki的对象存放在相应的哈希地址中
- 哈希冲突(同义词冲突):对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)
a. 与哈希表长度有关
b. 与装填因子有关
c. 与装填因子α=需要存储的数据个数/哈希表的大小有关,α越小,冲突可能性就越小
d. 与所采用的哈希函数有关 - 散列:散列技术就是数据保存的存储位置和关键字之间存在一个映射关系(哈希函数),使关键字和地址直接对应起来
哈希函数构造方式
- 直接地址法:
a. 直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法
b. 哈希函数表达式为:h(i)=k(i)+c(即关键字本身+常数值作为地址)
c. 优点:表达式简单,不会产生哈希冲突;缺点:可能会有大量的连续空间浪费 - 除留余数法:
a. 除留余数法是将关键字除以一个不小于哈希表长度m的数p所得的余数作为地址的方法
b. 哈希函数表达式:h(k)=k % p (p<=m,且p最好是质(素)数,一般选择小于m的最大质数)
哈希冲突的解决方法
-
开放地址法:
开放地址法是在发生哈希冲突时,寻找新地址的方法
a. 线性探测法:从发生冲突的地址开始,依次往后探测(当到达表尾时,下一探测位置为首地址)
数学描述公式:
b. 平方探测法:可以避免出现堆积问题
数学描述公式:
-
拉链法:
拉链法是将所有同义词用单链表链接起来的方法,哈希表每个单元不在是关键字本身,而是相应同义词单链表的首节点指针
a. 优点:(1)哈希链处理冲突简单,且无堆积现象,平均操查找长度较短 (2)适用与表长无法确定的情况 (3)空间利用率高 (4)删除结点操作简单
b. 缺点:指针需要的额外空间,数据规模较小时一般选择开放地址法
哈希链的操作
- 插入及创建:
- 开放地址法
typedef struct
{
KeyType key; //关键字域
int count; //探测次数域
}HashTable; //哈希表单位类型
int InsertHT(HashTable ha,int p,int k,int &n)
{
int adr,i;
adr=k % p;
if(adr==NULLKEY || adr==DELKEY) //地址为空,可插入数据
{
ha[adr].key=k;ha[adr].count=1;
}
else
{
i=1;
while(ha[adr].key!=NULLKEY && ha[adr].key!=DELKEY)
{
adr=(adr+1) % m;
i++;
}//查找插入位置
ha[adr].key=k;ha[adr].count=i; //找到插入位置
}
n++;
}
void CreatHT(HashTable ha,int &n,int m,int p,KeyType keys[],int nl)
{
for(int i=0;i<m;i++
{
ha[i].key=NULLKEY;
ha[i].count=0;
}
n=0;
for(i=0;i<nl;i++)
InsertHT(ha,p,keys[i],int &n);
}
- 拉链法:
typedef struct node
{
KeyType key; //关键字域
struct node*next; //下一个结点指针
}NodeType; //单链表结点类型
typedef struct
{
NodeType *firstp; //首结点指针
}HashTable; //哈希表单元类型
void InsertHT(HashTable ha[],int &n,int p,KeyType k) //将关键字k插入到哈希表中
{
int adr;
adr=k%p; //计算哈希函数值
NodeType *q;
q=(NodeType*)malloc(sizeof(NodeType));
q->key=k; //创建一个结点q,存放关键字k
q->next=NULL;
if(ha[adr].firstp==NULL)
ha[adr].firstp=q;
else //若单链表adr不为空
{
q->next=ha[adr].firstp; //采用头插法插入到ha[adr]的单链表中
ha[adr].firstp=q;
}
n++; //哈希表中结点的总个数增1
}
void CreatHT(HashTable ha[],int &n,int m,int p,KeyType keys[],int nl) //由关键字序列keys[0..nl-1]创建哈希表
{
for(int i=0;i<m;i++) //哈希表置初值
ha[i].firstp=NULL;
n=0;
for(i=0;i<nl;i++)
InsertHT(ha,n,p,keys[i]); //插入n个关键字
}
- 查找:
-
开放地址法:
-
拉链法:
几种解决哈希冲突的方法的平均查找长度的比较:
1.2 谈谈你对查找的认识及学习体会。
查找中需要应用到之前学习过的一些数据结构还有算法,本章新学习到的查找算法相较于之前学习过的,在时间效率上有显著优势。在数据的储存是采用合适的结构能够使查找效率提高很多。
学习了三大快的查找,分别是顺序表查找、树表查找还有哈希表查找,了解到了二叉排序树和AVL树在查找中的应用,B-树B+树在索引文件系统、数据库和磁盘中的应用。在进行其他操作之前要做的就是查找,所以好的查找算法很重要。
2.PTA题目介绍
2.1 7-5(哈希链) 航空公司VIP客户查询
2.1.1 该题的设计思路
2.1.2 该题的伪代码
文字+代码简要介绍本题思路
2.1.3 PTA提交列表
2.1.4 本题设计的知识点
1,2,3罗列。。。
2.2 题目2(2分)
2.3 题目3(2分)
下面同上