真题分析
年份 | 填空 | 选择 | 判断 | 综合 | 代码 |
---|---|---|---|---|---|
13 | 2 | 1 | 1 | 2 | 0 |
14 | 3 | 1 | 5 | 2 | 0 |
15 | 3 | 1 | 2 | 2 | 0 |
16 | 1 | 1 | 3 | 2 | 2 |
17 | 2 | 1 | 3 | 4 | 1 |
18 |
静态查找表和动态查找表
动态查找表还可以进行插入和删除操作
折半查找
性质
要求线性表以顺序方式存储,且元素有序。
查找的判定树为一颗「平衡二叉树」
比较流程
最大查找长度
将二分查找的方式类比二叉树,可知时间复杂度和类似于树的深度,即,查找过程和给定值比较的关键字个数最多为,这种描述查找过程的二叉树为判定树。
如果给判定树的所有叶子结点加上外部结点,则查找不成功的时候就是从根结点走到了外部结点,比较的次数最大和最小相差一
有序表查找,判定树,平均查找长度,比较次数
计算平均查找长度时我们可以画出查找的二叉树,查找成功时,平均查找长度为每层的结点数乘以层数之和/结点数,也可以 1248 依次乘以每一层
比较次数就依次折半查找即可,偶数位的中位数选择左边的
❝(17)画出在有序表 A[1..15]中进行折半查找的判定树,并求出查找成功的平均查找长度
❞
![e3f401115fd86013673e619e628d39b2.png](https://i-blog.csdnimg.cn/blog_migrate/a362b1a679691ac7b81d14b5559eda35.png)
查找成功平均查找长度(1+2x2+3x4+4x8)/15 = 49/15
关于折半查找判定树,可以通过中序填入有序序列,然后看取中是否符合要求来判断
索引顺序表查找(分块查找)
给顺序表分块,保证前一个块的所有数字都比后面一个块最小的要小,记录块的最大关键字和子表起始位置指针
先确定待查记录所在的块(子表),然后在块中顺序查找。
索引法平均查找长度
ASL 为上面两次查找方式之和(二分查找块和顺序查找块中关键字),取长度为 n,每块记录数为 s,块数 b = n/s 上取整,则 asl 为(b + 1)/2 + (s + 1)/2 = 。
易知平均查找长度和表长以及块中记录数 s 有关,当 s=时,asl 取最小值
二叉排序树 BST 的性质
二叉排序树「中序遍历」有序,不存在关键字的值相同
二叉排序树的插入和删除
❝(06)给定表(17,25,21,9,10,7,3,43),依次插入空的二叉排序树,画出二叉排序树
❞
![4042c426ecd0095a0f8da03c5dcd7732.png](https://i-blog.csdnimg.cn/blog_migrate/237929ae655072b3f233abeb664e3f69.png)
每次插入结点都是插入到叶子结点,不必移动其他结点
插入元素的平均复杂度为,比较的次数不会超过树的深度
二叉排序树删除结点要保留二叉排序树的特性
- 若删除的为叶子结点,直接删除即可
- 若删除的结点存在对应的单边子树,则直接让子树接到根结点
- 若删除的结点存在左右子树,则不能简单处理,有两种处理方法
![99b346b5598c446b46c2079e10bab6ca.png](https://i-blog.csdnimg.cn/blog_migrate/ce294e46037b460ff2176aa324734ec3.png)
平衡二叉树 AVL
一定是二叉排序树,插入操作会引起至少一个节点的平衡因子发生变化,左右子树高度差绝对值不超过 1
深度为 n 的平衡二叉树的最少结点数为 F(n + 2) - 1,其中 F 为斐波那契数列 1,1,2,3,5
建立平衡二叉树
先依次插入结点,直到平衡因子不符合要求,选取最下方不符合要求的定为 A,然后进行四种调整;注意每次插入结点的步骤要写好
![325ccd84fde00eb6b7758f8ecdac87f8.png](https://i-blog.csdnimg.cn/blog_migrate/8c80d2fe5d523aaa966fe5a236367f50.png)
❝(13)顺序输入关键字序列(70,80,90,50,40,65,68),画出构成的平衡二叉树,给出过程
❞
![5e3f56b55f3d020b7d0652cbeb6fb725.png](https://i-blog.csdnimg.cn/blog_migrate/38fb3c7a2fe5242aedc6fb616fc89a8b.png)
❝(14)顺序输入关键字序列(50,60,70,30,20,45),画出构成的平衡二叉树,给出过程 (15)顺序输入关键字序列(40,30,20,50,45,60),画出构成的平衡二叉树,给出过程 (16)已知一组关键字为(20,36,48,95,53,100,120,60,15,30,25),直接画出构成的二叉排序树和平衡二叉树
❞
注意这里存在调整一次不满足要求的情况,需要多次调整
![86522529728bbfa9d8eb3823c69ddbe3.png](https://i-blog.csdnimg.cn/blog_migrate/d38edc9bdbe29723592fead97d72ee01.png)
❝(17)顺序输入关键字序列(70,80,90,50,40,65,68),画出构成的平衡二叉树,给出过程
❞
平衡树查找的分析
深度为 n 时,最多查找次数是,在平衡树上查找的时间复杂度为 logn
b-树和 b+树
m 阶 b-树定义
- 每个结点至多有 m 棵子树,m-1 个关键字个数
- 若根结点不是叶子节点,则至少有 2 棵子树,1 个关键字
- 「除根结点之外」的所有非终端节点至少有个子树,结点关键字最少
- 所有的叶子结点都在同一层上,表示查找失败的结点,实际上不存在,指向这些结点的指针为 null
一些有关 b 树性质的计算:
高度为 h 的 m 阶 b 树的最少最多结点数,结点最少时形状类似于一棵满二叉树,结点最多时形状类似于一棵满 m 叉树
n 个非叶结点的 m 阶 b 树至少包含
高度为 2 的 5 阶 b 树,所含关键字个数至少是 5,根结点只有达到 5 个关键字时才能产生分裂
给出阶数和关键字数,最大高度公式 ,最小高度公式为
证明(17) m 阶 b- 树所具有的最少节点数
深度为 l + 1 的 m 阶 b- 树所具有的最少节点数
证明如下,第一层至少有 1 个结点,第二层至少有 2 个结点,每个非终端结点至少有个结点,所以第 3 层至少有个结点,以此类推。
所以最坏的情况,查找涉及的结点数不超过
插入
首先在非终端结点尝试插入,若个数不超过 m-1 个则直接插入
否则结点需要分裂为 m-1 个节点,关键字与指针插入双亲结点
![8db6040b65fc230a89271845b9f23e0d.png](https://i-blog.csdnimg.cn/blog_migrate/b6c3caaced9233d791c7638d1f7399a8.png)
删除
- 非终端结点,替代再删
- 终端结点
- 相邻兄弟移到双亲
- 双亲下移删除位置
- 个数不少于 直接删
- 等于 相邻大于
- 都等于
- 剩余和双亲转为兄弟
- 若导致双亲少于 就一直重复处理
![e9e65499b8ed7307c8bb4ecd2023f82a.png](https://i-blog.csdnimg.cn/blog_migrate/1db8dca1598b91fadd9e11d183bbe2b5.png)
b+树
与 b-树的差异
- n 棵子树的结点中含有 n 个关键字
- 叶子结点包含全部关键字的信息,以及指向这些关键字记录的指针,叶子结点自身依照关键字的大小自小而大的排列
- 所有非终端结点可以看作索引,结点中只含有其子树中的最大或最小关键字
b+树可以支持顺序检索,b-树不可以
哈希表
哈希查找是一种「随机查找」方法
不同的哈希函数得到同一哈希地址,这种现象叫做冲突
如果两个关键字的值不等,但是哈希函数值相等,则称这两个关键字为同义词
为了有效的应用 hash 查找技术,需要解决的两个问题,设定一个好的 hash 函数和处理冲突的方法
堆积问题是由于同义词或非同义词之间产生冲突导致的
构造方法
直接定址法 所得集合和关键字集合的大小相同,所以不同的关键字不会发生冲突
数字分析法 关键字都是以 r 为基的数,可能出现的关键字都是事先知道的,取关键字的若干位组成哈希地址
平方取中法 关键字平方后取中间几位作为哈希地址
折叠法 分割成几部分,然后取这几部分的叠加和作为哈希地址
除数取余法 除数选择最接近存储空间的素数。
随机数法 关键字长度不等时选用
解决冲突方法
开放定址法
- 线性探测再散列(每次往右移动)(处理同义词冲突时,添加了非同义词的冲突)(哈希表未满时保证可以找到不发生冲突的地址)
- 二次探测再散列(+1,-1,+4,-4,+9)
- 伪随机数再散列
链地址法
再哈希法,计算另一个哈希函数地址(不易产生聚集,但是增加了计算时间)
建立一个公共溢出区,只要发生冲突,就填入溢出表
注意再哈希法和开放定址法的组合考察,即设置两个 mod 函数,第一次冲突时,不进行探测
开放定址法处理冲突时,删除元素只能进行逻辑删除,不能物理删除
查找及其分析
三个因素
- 哈希函数
- 处理冲突的方法
- 哈希表的装填因子
采用开放定址法平均查找长度高于链地址法
负载因子(装填因子)α 定义为表中填入的记录数和哈希表的长度,反应哈希表的装满程度;越小,发生冲突的可能性就越小
哈希链地址法解决冲突的哈希表中,查找的平均查找长度直接与「装填因子」有关,而不直接依赖于记录数或者散列表长度
哈希表的平均查找长度是 α 的函数,而非 n 的函数。不管 n 多大,我们都能选择一个合适的 α 将平均查找长度限定在一个范围里
非链地址处理冲突的哈希表中,删除一个记录需要在该位置填入特殊符号,防止找不到同义词记录
画出哈希表,求等概率查找成功与不成功的平均查找长度
注意选择的处理冲突方式
❝(06)散列表的长度 m = 13,散列函数为 K mod m,给定关键码序列(19,14,23,01,68,20,84,27,55,11)线性探测再散列
❞
可以在线性探测法解决冲突的同时标记下探测次数,方便计算
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
14 | 1 | 68 | 27 | 55 | 19 | 20 | 84 | 23 | 11 | |||
1 | 1 | 1 | 2 | 1 | 1 | 3 | 4 | 3 | 1 |
查找成功 ASL = 1.8
不成功情况就是对哈希函数对应的查找失败情况依次查找(注意并非对于哈希表中所有情况进行查找,可能容量大于哈希函数的取值),「分母是哈希表容量」。
查找失败在空位置时为 1,其他位置则需要找到下一个空位置
查找失败 ASL = (1 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 3 + 2 + 1) / 13 = 4
❝(07)哈希函数 K mod 12,键值序列为(25,37,52,43,84,99,120,15,26,11,70,82),链地址法解决冲突
❞
![0970a22297ee7723c04f83b77f653d71.png](https://i-blog.csdnimg.cn/blog_migrate/f3bc565c94d870fccbef1e61c81e3466.png)
这里不限制横竖的方向,注意哈希表是连接在一起的,以及表尾的「空指针处理」。
链地址解决冲突的话,空位置查找长度为 0,此时不成功平均查找长度的快速计算方法是,使用关键字个数 / 哈希表容量
查找成功 (1 x 8 + 2 x 4) / 12 = 4 / 3
❝(13)哈希函数 K mod 11,关键字序列(33,52,64,57,41,24,12,78),平方探测再散列解决冲突 (14)哈希函数 K mod 13,关键字序列(12,51,8,22,26,80,11,16,54,41),线性探测解决冲突 (15)哈希函数 K mod 13,关键字序列(12,51,8,22,26,80,11,16,79,55),链地址法解决冲突 (16)0-15 的散列区,对十二个月份关键构造哈希表,哈希函数为 i/2 下取整,i 为关键字中第一个字母在字母表中的序号,线性探测开放定址处理冲突 (17)关键字(9,01,23,14,55,20,84,27)采用哈希函数 k mod 7 表长为 10,用开放定址法的二次探测再散列的方法,Hi = (H(key)+di)mod 10 解决冲突
❞
算法要求
算法名称 | 对于书上位置 | 算法编号 | 真题 |
---|---|---|---|
「二分查找」(递归) | 220 | 9.2 | 07,16,17 |
结点在二叉排序树中的层数 | (227) | ||
判断是否为二叉排序树 | |||
建立链式存储结构的二叉排序树 | |||
递减打印二叉排序树 | |||
「二叉排序树插入」 | 228 | 9.6 | |
「二叉排序树查找」 | 228 | 9.5 | |
「哈希表查找,插入」 | 259 | 9.17 9.18 | 16 |
二分查找递归形式
int SearchBin(SSTable A, int low, int high, KeyType K) {
if (low <= high) {
int mid = (low + high) / 2;
if (K == A[mid].key) return mid;
else if (K return SearchBin(A, low, mid - 1, K);
else return SearchBin(A, mid + 1, high, K);
} else return -1;
}
哈希表的建立,查找,插入
typedef struct node {
int key;
struct node *next;
} CHAINH;
// 尾插法建立哈希表,链地址解决冲突
void Unkown1(CHAINH *HTC[]) {
CHAINH *p;
int i, j;
i = 0;
scanf("%d", &i);
while (i != -99) {
j = i % 7;
p = (CHAINH *)malloc(sizeof(CHAINH));
p->next = HTC[j];
p->key = i;
HTC[j] = p;
scanf("%d", &i);
}
}
// 查找哈希表中是否含有k,存在的话返回1,不存在的话返回0
int Unkown2(CHAINH *HTC[], int k) {
CHAINH *p;
int j;
j = k % 7;
p = HTC[j];
if (p != NULL) {
while ((p->key != k) && (p->next != NULL)) {
p = p->next;
}
if (p->key == k)
return 1;
else
return 0;
}
return 0;
}
// 将结点i插入哈希表HTC
void Unkown3(CHAINH *HTC[], int i) {
CHAINH *p;
int j;
j = i % 7;
p = (CHAINH *)malloc(sizeof(CHAINH));
p->next = HTC[j];
p->key = i;
HTC[j] = p;
}