第9章 检索

第9章 检索

数据结构与算法_师大完整教程目录(更有python、go、pytorch、tensorflow、爬虫、人工智能教学等着你):https://www.cnblogs.com/nickchen121/p/13768298.html

一、检索的基本概念

  1. 检索:确定数据元素集合中是否存在数据元素等于特定元素或是否存在元素满足某种给定特征的过程

二、线性表的检索

2.1 顺序检索

  1. 注:暴力搜索,不多赘述
  2. 顺序检索时平均查找次数:\(ASL_{seq}=(n+1)/2\)

2.2 二分法检索(折半查找)

  1. 线性表结构:二分法检索需要线性表结点已经按其关键字从小到大(或从大到小)排序
  2. 二分法检索时平均查找次数:\(ASL_{bins}\approx{log_2(n+1)-1}\)

2.2.1 二分法检索(非递归实现)(真题)(算法)

  1. 算法步骤:

    1. 获取二分之后的中间结点的序号 \(mid\)
    2. 让待查找的数据元素 \(key\) 和中间结点 \(a[mid]\) 比较,成功则直接返回
    3. 失败之后,判断数据元素和中间结点的大小

如果中间结点大于数据元素,则在前半部分继续二分检索,\(right\) 变成 \(mid-1\)\(mid-1\)是因为 \(mid\) 已经做出过判断,不需要再次比较)
如果中间结点小于数据元素,则在后半部分继续二分检索,\(left\) 变成 \(mid+1\)

int binsearch(int a[], int left, int right, int x) {
    int mid;
    while (left <= right) {
        mid = (left + right) / 2; // 二分
        if (a[mid] == x) return mid; // 检索成功返回
        if (a[mid] > x) right = mid - 1; // 继续在前半部分进行二分检索
        else left = mid + 1; // 继续在后半部分进行二分检索
    }
    return -1; // 当 left>right 时表示查找区间为空,检索失败
}

2.3 分块检索

  1. 分块检索思想:把线性表分成若干块,每一块中,结点的存放不一定有序,但块与块之间必须是分块有序的(第一块中的结点的值都小于第二块中的结点值;第二块中的结点值都小于第三块中的结点值…)

  2. 分块查找时平均查找长度为:假设线性表中共有 \(n\) 个元素,且被均分成 \(b\) 块,则每块中的元素个数 \(s=n/b\),待查元素在索引表中的平均查找长度为 \(E_1\),块内查找时所需的平均查找长度为 \(E_b\)

    1. 在顺序检索来确定块时,分块查找成功时的平均查找长度为 \(ASL_{ids}=E_1+E_b=\frac{b+1}{2}+\frac{s+1}{2}=\frac{n/s+s}{2}+1=\frac{n+s^2}{2s}+1\)

\(s=\sqrt{n}\) 时,\(ASL_{ids}\) 取最小值 \(\sqrt{n}+1\) (最佳查找长度)
3. 在二分检索来确定块时,分块查找成功时的平均查找长度为 \(ASL'_{ids}=E_1+E_b\approx{log_2(b+1)}-1+\frac{s+1}{2}\approx{log_2(\frac{n}{s}+1)+\frac{s}{2}}\)
3. 算法步骤:

1. 建立索引表(数组):

索引表结点中存储两个元素:一个元素表示某一块在原数组中的开始下标;另一个元素表示某一块中最大的值
3. 让被查找值和最大的值进行比较,获取查找值在数组中比较的下标范围
4. 最后在该范围内进行顺序查找即可
4. 图分块检索:

2.3.1 分块检索索引表存储结构

typedef int datatype;
// 索引表结点类型
typedef struct {
    datatype key;
    int address;
} indexnode;

三、二叉排序树

  1. 二分检索法的缺陷:二分检索法虽然有较高的效率,但是要求被查找的一组数据有序,因此在这一组数据中增添删除很麻烦

  2. 二叉排序树解决的问题:在检索过程中不需要被查找的数据有序,即可拥有较高的查找效率,实现在数据查找过程中的增添和删除

  3. 二叉排序树的性质:

    1. 左子树非空时,左子树上的所有结点的值都小于根结点的值
    2. 右子树非空时,右子树上的所有结点的值都大于根结点的值
    3. 它的左、右子树本身又各是一颗二叉排序树
  4. 注:当二叉排序树只有左(右)结点时,退化成一个有序的单链表(此时应该用二分检索法进行检索)

  5. 注:对二叉排序树进行中序遍历时可以得到按结点值递增排序的结点序列

  6. 注:不同关键码构造出不同的二叉排序树的可能性有 \(\frac{1}{n+1}C_{2n}^{n}\)

  7. 常用操作:

    1. 基于二叉排序树的结点的删除

3.1 二叉排序树的存储结构

typedef int datatype;
// 二叉排序树结点定义
typedef struct node {
    datatype key; // 结点值
    struct node *lchild, *rchild; // 左、右孩子指针
} bsnode;
typedef bsnode *bstree;

3.2 基于二叉排序树检索算法(算法)

  1. 算法步骤:

    1. 当二叉树为空树时,检索失败
    2. 如果二叉排序树根结点的关键字等于待检索的关键字,则检索成功
    3. 如果二叉排序树根结点的关键字小于待检索的关键字,则用相同的方法继续在根结点的右子树中检索
    4. 如果二叉排序树根结点的关键字大于待检索的关键字,则用相同的方法继续在根结点的左子树中检索
typedef int datatype;
// 二叉排序树结点定义
typedef struct node {
    datatype key; // 结点值
    struct node *lchild, *rchild; // 左、右孩子指针
} bsnode;
typedef bsnode *bstree;

// 递归实现
void bssearch1(bstree t, datatype x, bstree *f, bstree *p) {
    // *p 返回 x 在二叉排序中的地址;*f 返回 x 的父结点位置
    *f = NULL;
    *p = t;
    while (*p) {
        if (x == (*p)->key) return;
        *f = *p;
        *p = (x < (*p)->key) ? (*p)->lchild : (*p)->rchild;
    }
    return;
}

// 非递归实现
bstree bssearch2(bstree t, datatype x) {
    if (t == NULL || x == t->key) return t;
    if (x < t->key) return bssearch2(t->lchild, x); // 递归地在左子树检索
    else return bssearch2(t->rchild, x); // 递归地在右子树检索
}

3.3 基于二叉排序树的结点的插入算法(算法)

  1. 算法步骤:

    1. 循环查找插入位置的结点
    2. 若二叉排序树为空,则生成一个关键字为 \(x\) 的新结点,并令其为二叉排序树的根结点
    3. 否则,将待插入的关键字 \(x\) 与根结点的关键字进行比较,若二者相等,则说明树中已有关键字 \(x\),无须插入
    4. \(x\) 小于根结点的关键字,则将 \(x\) 插入到该树的左子树中,否则将 \(x\) 插入到该树的右子树
    5. \(x\) 插入子树的方法与在整个树中的插入方法是相同的,如此进行下去,直到 \(x\) 作为一个新的叶结点的关键字插入到二叉排序树中,或者直到发现树中已有此关键字为止
typedef int datatype;
// 二叉排序树结点定义
typedef struct node {
    datatype key; // 结点值
    struct node *lchild, *rchild; // 左、右孩子指针
} bsnode;
typedef bsnode *bstree;

void insertbstree(bstree *t, datatype x) {
    bstree f = NULL, p;
    p = *t;

    // 查找插入位置
    while (p) {
        if (x == p->key) return; // 若二叉排序中已有 x,无需插入
        f = p; // *f 用于保存新结点的最终插入位置
        p = (x < p->key) ? p->lchild : p->rchild;
    }

    // 生成待插入的新结点
    p = (bstree) malloc(sizeof(bsnode));
    p->key = x;
    p->lchild = p->rchild = NULL;

    // 原树为空
    if (*t == NULL) *t = p;
    else if (x < f->key) f->lchild = p;
    else f->rchild = p;
}

3.4 生成一颗排序二叉树

  1. 算法步骤:

    1. 循环输入关键字,然后使用 \(3.3\) 的插入算法把关键字插入二叉排序树
  2. 图生成二叉排序树:

四、丰满树和平衡树

  1. 丰满树和平衡树解决的问题:保证在树中插入或删除结点时保持树的 “平衡”,使之尽可能保持二叉树的性质又保证树的高度尽可能地为 \(O(log_2n)\)

4.1 丰满树(大纲未规定)

  1. 丰满树:任意两个非双孩子结点的高度之差的绝对值要小于等于 \(1\),即子女结点个数小于 \(2\) 的结点只出现在树的最低两层中

  2. 常用操作:

    1. 建立丰满树

4.2 平衡二叉排序树

  1. 平衡二叉树(\(AVL\) 树 ):它的左子树和右子树都是平衡二叉树,**且左子树和右子树的高度之差的绝对值不超过 \(1\) **

  2. 平衡因子:结点的左子树高度与右子树高度之差(平衡二叉树的任意结点的平衡因子绝对值小于等于 \(1\)

  3. 丰满树和平衡树:丰满树一定是平衡树,平衡树却不一定是丰满树

  4. 常用操作:

    1. 基于 \(AVL\) 树 的结点插入算法
  5. 平衡二叉排序树最大深度求法:假设 \(N_h\) 表示深度为 \(h\) 的平衡二叉树中含有的最少的结点数目,那么,\(N_0=0\)\(N_1=1\)\(N_2=2\),并且 \(N_h=N_{h-1}+N_{h-2}+1\)

4.3 AVL树调整平衡的方法

4.3.1 LL型平衡旋转

  1. \(LL\) 型 平衡旋转:

    1. 由于在 \(A\) 的左孩子的左子树上插入新结点,使 \(A\) 的平衡度由 \(1\) 增至 \(2\) ,致使以 \(A\) 为根的子树失去平衡,如图\(9.9(a)\)
    2. 示此时应进行一次顺时针旋转,“提升” \(B\)\(A\) 的左孩子)为新子树的根结点
    3. \(A\) 下降为 \(B\) 的右孩子
    4. \(B\) 原来的右子树 \(B_r\) 调整为 \(A\) 的左子树
  2. 图LL型平衡旋转

4.3.2 RR型平衡旋转

  1. \(RR\) 型 平衡旋转:

    1. 由于在 \(A\) 的右孩子的右子树上插入新结点,使A的平衡度由 \(-1\) 变为 \(-2\),致使以 \(A\) 为根的子树失去平衡,如图 \(9.9(b)\) 所示
    2. 此时应进行一次逆时针旋转,“提升” \(B\)\(A\) 的右孩子)为新子树的根结点
    3. \(A\) 下降为 \(B\) 的左孩子
    4. \(B\) 原来的左子树 \(B_L\) 调整为 \(A\) 的右子树
  2. 图RR型平衡旋转

4.3.3 LR型平衡旋转

  1. \(LR\) 型 平衡旋转(\(A\) 的左孩子的右孩子(\(LR\))插入 \(A\)\(A\) 的左孩子 \(B\) 之间,之后做 \(LL\) 型 平衡旋转):

    1. 由于在 \(A\) 的左孩子的右子树上插入新结点,使 \(A\) 的平衡度由 \(1\) 变成 \(2\),致使以 \(A\) 为根的子树失去平衡,如图 \(9.9(c)\) 所示
    2. 此时应进行两次旋转操作(先逆时针,后顺时针),即 “提升” \(C\)\(A\) 的左孩子的右孩子)为新子树的根结点
    3. \(A\) 下降为 \(C\) 的右孩子
    4. \(B\) 变为 \(C\) 的左孩子
    5. \(C\) 原来的左子树 \(C_L\) 调整为 \(B\) 现在的右子树
    6. \(C\) 原来的右子树 \(C_r\) 调整为 \(A\) 现在的左子树
  2. 图LR型平衡旋转

4.3.4 RL型平衡旋转

  1. \(RL\) 型 平衡旋转(\(A\) 的右孩子的左孩子(\(RL\))插入 \(A\)\(A\) 的右孩子 \(B\) 之间,之后做 \(RR型\) 平衡旋转):

    1. 由于在 \(A\) 的右孩子的左子树上插入新结点,使A的平衡度由 \(-1\) 变成 \(-2\),致使以 \(A\) 为根的子树失去平衡,如图 \(9.9(d)\)所示
    2. 此时应进行两旋转操作(先顺时针,后逆时针),即 “提升” $ C$(即 \(A\) 的右孩子的左孩子)为新子树的根结点
    3. \(A\) 下降 \(C\) 的左孩子
    4. \(B\) 变为 \(C\) 的右孩子
    5. \(C\) 原来的左子树 \(C_L\) 调整为 \(A\) 现在的右子树
    6. \(C\) 原来的右子树 \(C_r\) 调整为 \(B\) 现在的左子树。
  2. 图RL型平衡旋转

4.4 生成一颗平衡二叉排序树

  1. 算法步骤:

    1. 插入:不考虑结点的平衡度,使用在二叉排序树中插入新结点的方法,把结点 \(k\) 插入树中,同时置新结点的平衡度为 \(0\)
    2. 调整平衡度:假设 \(k0,k1,…,km=k\) 是从根 \(k_0\) 到插入点 \(k\) 路径上的结点,由于插入了结点 \(k\),就需要对这条路径上的结点的平衡度进行调整(调整平衡度参考上述四种(\(LL、RR、LR、RL\))方法)
    3. 改组:改组以 \(k_j\) 为根的子树除了满足新子树高度要和原来以 \(k_j\) 为根子树的高度相同外,还需使改造后的子树是一棵平衡二叉排序树
  2. 图生成一颗AVL树

五、二叉排序树和Huffman树

5.1 扩充二叉树(大纲未规定)

5.2 二叉排序树(大纲未规定)

5.3 Huffman树

  1. 带权外部路径长度:\(WPL=\sum_{i=1}^{n}W_{ki}*(\lambda{k_i})\)

    1. \(n\) 个结点 \(k_1,k_2,\cdots,k_n\),它们的权分别是 \(W(k_i)\)
    2. \(\lambda{k_i}\) 是从根结点到达外部结点 \(k_i\) 的路径长度
  2. \(huffman\) 树:具有最小带权外部路径长度的二叉树

  3. 算法步骤:

    1. 根据给定的 \(n\) 个权值 \(\{w_1,w_2,\cdots,w_n\}\) 构造 \(n\) 棵二叉树的集合 \(F=\{T_1,T_2,\cdots,T_n\}\),其中每棵二叉树 \(T_i\) 中只有一个带权为 \(w_i\) 的根结点,其左、右子树均为空
    2. \(F\) 中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点权值为其左、右子树根结点的权值之和
    3. \(F\) 中用新得到的二叉树代替这两棵树
    4. 重复步骤 \(2、3\),直到 \(F\) 中只含有一棵树为止

5.3.1 构造Huffman树

  1. 对于结点序列 \(6、10、16、20、30、24\) ,构造 \(huffman\) 树的过程如下:
  2. 图构造huffman树:

5.3.2 通过Huffman算法构造编码树

  1. 注:出现频率越大的字符其编码越短
  2. 图huffman编码:
  3. 字符平均编码长度为:\(((6+10)*4+16*3+(20+24+30)*2)/106=2.45\)

六、B树

  1. B树:称为多路平衡查找树,也称为 “B-树”,主要针对较大的、存放在外存储器上的文件,适合在磁盘等直接存取设备上组织动态的索引表

6.1 B-树的定义

  1. B-树:一种平衡的多路查找树,一颗 \(m(m\geq{3})\) 阶的B-树,或为空树,或为满足下列特性的 \(m\) 叉树

    1. 树中的每个结点至多有 \(m\) 棵子树
    2. 若根结点不是叶子结点,则至少有两棵子树
    3. 所有的非终端结点中包含下列信息 \((sn,p_0,k_1,p_1,k_2,p_2,\ldots,k_n,p_n)\)

其中 \(k_i(1\leq{i}\leq{n})\) 为关键字,且 \(k_i<k_i+1(1\leq{i}\leq{n})\)
\(p_j(0\leq{j}\leq{n})\) 为指向子树根结点的指针,且 \(p_j(0\leq{j}<n)\) 所指子树中所有结点的关键字均小于 \(k_j+1\)
\(p_n\) 所指子树中所有结点的关键字均大于 \(k_n\)
\(n(\lceil{m/2}\rceil-1\leq{n}\leq{m-1})\) 为关键字的个数(\(n+1\) 为子树个数)
8. 除根结点之外所有非终端结点至少有 棵子树,也即每个非根结点至少应有 \(\lceil{m/2}\rceil-1\) 个关键字
9. 所有的叶子结点都出现在同一层上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)
2. 图3阶B-树:

6.2 B-树的基本操作

  1. 基于B-树的查找
  2. 基于B-树的插入运算
  3. 基于B-树的删除运算

6.3 B+树(大纲未规定)

七、散列表检索

7.1 散列存储

  1. 散列存储的基本思想:以关键码的值为变量,通过一定的函数关系(称为散列(\(Hash\))函数),计算出对应的函数值,以这个值作为结点的存储地址

  2. 冲突:两个不同的关键字具有相同的存放地址

  3. 负载因子:\(\frac{散列表中结点的数目}{基本区域能容纳的结点树}\)

    1. 注:负载因子越小,空间浪费越多;负载因子越大,冲突可能性越高(负载因子大于 \(1\) 时,一定会有冲突)

7.2 散列函数的构造

  1. 除余法(大概率):使用略小于 \(Hash\) 地址集合中地址个数 \(m\) 的质数 \(p\) 来除关键字

    1. 散列函数: \(H(key) = key\%p\)
    2. 注:除余法的 \(p\) 的选择不当,容易发生冲突
  2. 平方取中法:取关键字平方后的中间几位为 \(Hash\) 地址,所取的位数和 \(Hash\) 地址位数相同

  3. 折叠法:让关键字分割成位数相同的几部分,然后选取这几部分的叠加和作为 \(Hash\) 地址

  4. 数字分析法:对于关键字的位数比存储区域的地址码位数多的情况下,对关键字分析,丢掉分布不均匀的位,留下分布均匀的位作为 \(Hash\) 地址

  5. 直接地址法(大概率):取关键字或关键字的某个线性函数值为哈希地址

    1. 散列函数:\(H(key)=key\,或\,H(key)=a*key+b\)
    2. 注:直接地址法对于不同的关键字,不会产生冲突,容易造成空间的大量浪费

7.3 冲突处理

  1. 开放定址法:发生冲突时,按照某种方法继续探测基本表中的其他存储单元,直到找到一个开放的地址(空位置)为止

    1. 开放定址法的一般形式:\(H_i(k) = (H(k)+d_i)\,mod\,m\),其中 \(H(k)\) 为关键字为 \(k\) 的直接哈希地址,\(m\) 为哈希表长,\(d_i\) 为每次再探测时的地址增量

线性探测再散列:\(d_i = 1,2,3,\cdots,m-1\)
二次探测再散列:\(d_i=1^2,{-1}^2,2^2,{-2}^2,\cdots,k^2,{-k}^2(k\leq{m/2})\)
随机探测再散列:\(d_i = 随机数序列\)
5. 聚集:几个 \(Hash\) 地址不同的关键字争夺同一个后继 \(Hash\) 地址的现象
6. 开放定址法容易发生聚集现象,尤其是采用线性探测再散列
7. 图开放定址法:
2. 再哈希法:某个元素 \(k\) 在原散列函数 \(H(k)\) 的映射下与其他数据发生碰撞时,采用另外一个 \(Hash\) 函数 \(H_i(k)\) 计算 \(k\) 的存储地址

1. 注:该方法不容易发生 “聚集”,但增加了计算的时间
  1. 拉链法:把所有关键字为同义词的结点链接在同一个单链表中,若选定的散列表长度为 \(m\),则可以把散列表定义为一个由 \(m\) 个头指针组成的指针数组 \(T[0\ldots{m-1}]\)凡是散列地址为 \(i\) 的结点,均插入到以 \(T[i]\) 为头指针的单链表中

    1. 注:拉链法的指针需要额外的空间,因此当结点规模较小时,开放定址法更加节省空间
    2. 图拉链法:
  2. 开放定址法、再哈希法、拉链法的比较

    1. | | 开放定址法 | 再哈希法 | 拉链法 |
      | :--: | :--: | :--: | :--: |
      | 优点 | 不需要额外的计算时间和空间 | 不易 “聚集” | 无 “聚集”;非同义词不会冲突 |
      | 缺点 | 容易发生 “聚集” 现象 | 增加了计算时间 | 需要额外的空间 |

7.4 散列表检索的应用

  1. 将关键字序列 \((7、8、30、11、18、9、14)\) 散列存储到散列表中。散列表的存储空间是一个下标从 \(0\) 开始的一维数组。散列函数为: \(H(key) = (key*3) MOD 7\),处理冲突采用线性探测再散列法,要求装载因子为 \(0.7\)

    1. 请画出所构造的散列表
    2. 分别计算等概率情况下查找成功和查找不成功的平均查找长度

八、查找算法的分析及应用(书中未给出)

九、算法设计题

9.1 在给定查找树中删除根结点值为 \(a\) 的子树(算法)

\(T\) 是一棵给定的查找树,试编写一个在树 \(T\) 中删除根结点值为 \(a\) 的子树的程序。要求在删除的过程中释放该子树中所有结点所占用的存储空间。这里假设树 T 中的结点采用二叉链表存储结构

  1. 算法步骤:

    1. 注:删除二叉树可以采用后序遍历方法,先删除左子树,再删除右子树,最后删除根结点
    2. 先在指定的树中查找值为 a 的结点,找到后删除该棵子树
typedef int datatype;
// 二叉树结点定义
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

// 删除以 t 为根的二叉树
void deletetree(bintree *t) {
    if (*t) {
        deletetree(&(*t)->lchild); // 递归删除左子树
        deletetree(&(*t)->rchild);// 递归删除右子树
        free(*t); // 删除根结点
    }
}

// 删除二叉树中以根结点值为 a 的子树
void deletea(bintree *t, datatype a) {
    bintree pre = NULL, p = *t;

    // 查找值为 a 的结点
    while (p && p->data != a)
    {
        pre = p;
        p = (a < p->data) ? p->lchild : p->rchild;
    }
    
    if (!pre) *t = NULL;    // 树根
    else  // 非树根
    if (pre->lchild == p) pre->lchild = NULL;
    else pre->rchild = NULL;
    deletetree(&p); // 删除以 p 为根的子树
}

9.2 判断给定二叉树是否为二叉排序树(算法)

试写一算法判别给定的二叉树是否为二叉排序树,设此二叉树以二叉链表为存储结构,且树中结点的关键字均不相同

  1. 算法步骤:

    1. 判定二叉树是否为二叉排序树可以建立在二叉树中序遍历的基础上,
    2. 在遍历中附设一指针 \(pre\) 指向树中当前访问结点的中序直接前驱,
    3. 每访问一个结点就比较前驱结点 \(pre\) 和此结点是否有序
    4. 若遍历结束后各结点和其中序直接前驱均满足有序,则此二叉树即为二叉排序树,否则不是二叉排序树
typedef int datatype;
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

// 函数 bisorttree()用于判断二叉树 t 是否为二叉排序树,
// 初始时 pre=NULL;flag=1;
// 结束时若 flag==1,则此二叉树为二叉排序树,否则此二叉树不是二叉排序树。
void bisorttree(bintree t, bintree *pre, int *flag) {
    if (t && *flag == 1) {
        bisorttree(t->lchild, pre, flag); // 判断左子树
        // 访问中序序列的第一个结点时不需要比较
        if (pre == NULL) {
            *flag = 1;
            *pre = t;
        } else { // 比较 t 与中序直接前驱 pre 的大小(假定无相同关键字)
            if ((*pre)->data < t->data) {
                *flag = 1;
                *pre = t;
            } else *flag = 0; //  pre 与 t 无序
        }
        bisorttree(t->rchild, pre, flag); // 判断右子树
    }
}

十、错题集

  1. 设顺序存储的线性表共有 \(123\) 个元素,按分块查找的要求等分成 \(3\) 块。若对索引表采用顺序查找来确定块,并在确定的块中进行顺序查找(寻找确定的块还需要 \(2\) 步),则在查找概率相等的情况下,分块查找成功时的平均查找长度为 \(23\)

  2. 有数据 \({53,30,37,12,45,24,96}\) ,从空二叉树开始逐步插入数据形成二叉排序树,若希望高度最小,则应该选择下列的序列输入,答案 \(37,24,12,30,53,45,96\)

    1. 要创建一颗高度最小的二叉排序树,就必须让左右子树的结点个数越接近越好

由于给定的是一个关键字有序序列 \(a[start\ldots{end}]\),让其中间位置的关键字 \(a[mid]\) 作为根结点
左序列 \(a[start\dots{mid-1}]\)
构造左子树
右序列 \(a[mid+1\ldots{end}]\) 构造右子树
3. 若在 \(9\) 阶 B-树中插入关键字引起结点分裂,则该结点在插入前含有的关键字个数为 \(8\)

1. 注:如果是 $m$ 阶,答案是 $m-1$
  1. 下列叙述中,不符合 \(m\) 阶B树定义要求的是叶结点之间通过指针链接
  2. 在分块检索中,对 \(256\) 个元素的线性表分成多少 \(16\) 块最好。每块的最佳长度(平均查找长度)\(17\),若每块
    的长度为 \(8\),其平均检索的长度在顺序检索时为 \(21\),在二分检索时为 \(9\)
1. 假设线性表中共有 $n$ 个元素,且被均分成 $b$ 块,则每块中的元素个数 $s=n/b$,待查元素在索引表中的平均查找长度为 $E_1$,块内查找时所需的平均查找长度为 $E_b$
 2. 在顺序检索来确定块时,分块查找成功时的平均查找长度为 $ASL_{ids}=E_1+E_b=\frac{b+1}{2}+\frac{s+1}{2}=\frac{n/s+s}{2}+1=\frac{n+s^2}{2s}+1$
  	1. 当 $s=\sqrt{n}$ 时,$ASL_{ids}$ 取最小值 $\sqrt{n}+1$
1. 在二分检索来确定块时,分块查找成功时的平均查找长度为 $ASL'_{ids}=E_1+E_b\approx{log_2(b+1)}-1+\frac{s+1}{2}\approx{log_2(\frac{n}{s}+1)+\frac{s}{2}}$
  1. 设有关键码 A、B、C 和 D,按照不同的输入顺序,共可能组成 \(14\) 种不同的二叉排序树

    1. 使用公式:不同关键码构造出不同的二叉排序树的可能性有 \(\frac{1}{n+1}C_{2n}^{n}\)
  2. 含有 \(12\) 个结点的平衡二叉树的最大深度是多少 \(4\)(设根结点深度为 \(0\))、\(5\)(设根结点深度为 \(1\)),并画出一棵这样的树

    1. 假设 \(N_h\) 表示深度为 \(h\) 的平衡二叉树中含有的最少的结点数目
    2. 那么,\(N_0=0\)\(N_1=1\)\(N_2=2\),并且 \(N_h=N_{h-1}+N_{h-2}+1\),根据平衡二叉树平衡二叉树的这一性质,\(N_5=12\),如果根结点深度为 \(0\),则最大深度为 \(4\)
    3. 图习题9-6:
posted @ 2020-10-04 20:11  B站-水论文的程序猿  阅读(1212)  评论(0编辑  收藏  举报