第9章 检索

第9章 检索

1、检索的基本概念

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

2、线性表的检索

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. 失败以后,判断数据元素和中间结点的大小
      1. 若是中间结点大于数据元素,则在前半部分继续二分检索,\(right\) 变成 \(mid-1\)\(mid-1\)是由于 \(mid\) 已经作出过判断,不须要再次比较)
      2. 若是中间结点小于数据元素,则在后半部分继续二分检索,\(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\)
      1. \(s=\sqrt{n}\) 时,\(ASL_{ids}\) 取最小值 \(\sqrt{n}+1\) (最佳查找长度)
    2. 在二分检索来肯定块时,分块查找成功时的平均查找长度为 \(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. 创建索引表(数组):
      1. 索引表结点中存储两个元素:一个元素表示某一块在原数组中的开始下标;另外一个元素表示某一块中最大的值
    2. 让被查找值和最大的值进行比较,获取查找值在数组中比较的下标范围
    3. 最后在该范围内进行顺序查找便可
  4. 图分块检索:

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

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

3、二叉排序树

  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. 图生成二叉排序树:

4、丰满树和平衡树

  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\) 型 平衡旋转:node

    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型平衡旋转app

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型\) 平衡旋转):spa

    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. 算法步骤:3d

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

5、二叉排序树和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. 重复步骤 \(二、3\),直到 \(F\) 中只含有一棵树为止

5.3.1 构造Huffman树

  1. 对于结点序列 \(六、十、1六、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\)

6、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)\)
      1. 其中 \(k_i(1\leq{i}\leq{n})\) 为关键字,且 \(k_i<k_i+1(1\leq{i}\leq{n})\)
      2. \(p_j(0\leq{j}\leq{n})\) 为指向子树根结点的指针,且 \(p_j(0\leq{j}<n)\) 所指子树中全部结点的关键字均小于 \(k_j+1\)
      3. \(p_n\) 所指子树中全部结点的关键字均大于 \(k_n\)
      4. \(n(\lceil{m/2}\rceil-1\leq{n}\leq{m-1})\) 为关键字的个数(\(n+1\) 为子树个数)
    4. 除根结点以外全部非终端结点至少有 棵子树,也即每一个非根结点至少应有 \(\lceil{m/2}\rceil-1\) 个关键字
    5. 全部的叶子结点都出如今同一层上,而且不带信息(能够看做是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)
  2. 图3阶B-树:

6.2 B-树的基本操做

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

6.3 B+树(大纲未规定)

7、散列表检索

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

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

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

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

7.4 散列表检索的应用

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

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

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

9、算法设计题

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); // 判断右子树
    }
}

10、错题集

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

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

    1. 要建立一颗高度最小的二叉排序树,就必须让左右子树的结点个数越接近越好
      1. 因为给定的是一个关键字有序序列 \(a[start\ldots{end}]\),让其中间位置的关键字 \(a[mid]\) 做为根结点
      2. 左序列 \(a[start\dots{mid-1}]\)
        构造左子树
      3. 右序列 \(a[mid+1\ldots{end}]\) 构造右子树
  3. 若在 \(9\) 阶 B-树中插入关键字引发结点分裂,则该结点在插入前含有的关键字个数为 \(8\)

    1. 注:若是是 \(m\) 阶,答案是 \(m-1\)
  4. 下列叙述中,不符合 \(m\) 阶B树定义要求的是叶结点之间经过指针连接

  5. 在分块检索中,对 \(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}}\)
  6. 设有关键码 A、B、C 和 D,按照不一样的输入顺序,共可能组成 \(14\) 种不一样的二叉排序树

    1. 使用公式:不一样关键码构造出不一样的二叉排序树的可能性有 \(\frac{1}{n+1}C_{2n}^{n}\)
  7. 含有 \(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: