本节课实验课总体安排:
1、讲解上节课布置的3道课后习题
2、设计1道算法题:折半插入排序
3、布置1道力扣题
4、布置下次课后作业:学习通,期末习题复习一
一、第七章课后习题讲解
【P286第4题】
折半查找有序表(4,6,10,12,20,30,50,70,88,100)。若查找表中元素 58,则它将依次与表中( )比较大小,查找结果是失败。
A.20,70,30,50 B.30,88,70,50
C.20,50 D.30,88,50
答案:A
解释:表中共 10 个元素,第一次向下取整(1+10)/2=5,与第五个元素 20 比较,58 大于20,再向下取整(6+10)/2=8,与第八个元素 70 比较,依次类推再与 30、50 比较,最终查找失败。
【第7题】
分别以下列序列构造二叉排序树,与用其它三个序列所构造的结果不同的是( )
A.(100,80, 90, 60, 120,110,130)
B.(100,120,110,130,80, 60, 90)
C.(100,60, 80, 90, 120,110,130)
D.(100,80, 60, 90, 120,130,110)
答案:C
解释:A、B、C、D 四个选项构造二叉排序树都以 100 为根,易知 A、B、D 三个序 列中第一个比 100 小的关键字为 80,即 100 的左孩子为 80,而 C 选项中 100 的左孩子为 60,故选 C。
这道题考察 二叉排序树(BST, Binary Search Tree) 的构造和序列的等价性。关键是:
-
二叉排序树的性质:对于每个节点,左子树上所有节点都小于它,右子树上所有节点都大于它。
-
等价二叉排序树:如果插入顺序不同,但构造出的树形状相同,就算等价。
步骤一:分析每个序列
序列 A: 100, 80, 90, 60, 120, 110, 130
-
插入 100 → 根节点
-
插入 80 → 左子节点
-
插入 90 → 80 的右子节点(80 < 90 < 100)
-
插入 60 → 80 的左子节点
-
插入 120 → 100 的右子节点
-
插入 110 → 120 的左子节点
-
插入 130 → 120 的右子节点
A 树形状:
100
/ \
80 120
/ \ / \
60 90 110 130
序列 B: 100, 120, 110, 130, 80, 60, 90
-
100 → 根节点
-
120 → 100 右子节点
-
110 → 120 左子节点
-
130 → 120 右子节点
-
80 → 100 左子节点
-
60 → 80 左子节点
-
90 → 80 右子节点
B 树形状:
100
/ \
80 120
/ \ / \
60 90 110 130
✅ 和 A 完全相同。
序列 C: 100, 60, 80, 90, 120, 110, 130
-
100 → 根节点
-
60 → 100 左子节点
-
80 → 60 右子节点
-
90 → 80 右子节点
-
120 → 100 右子节点
-
110 → 120 左子节点
-
130 → 120 右子节点
C 树形状:
100
/ \
60 120
\ / \
80 110 130
\
90
✅ 和 A、B 不同(左子树结构不同)。
序列 D: 100, 80, 60, 90, 120, 130, 110
-
100 → 根
-
80 → 100 左
-
60 → 80 左
-
90 → 80 右
-
120 → 100 右
-
130 → 120 右
-
110 → 120 左
D 树形状:
100
/ \
80 120
/ \ / \
60 90 110 130
【第14题】
设哈希表长为 14,哈希函数是 H(key)=key%11,表中已有数据的关键字为 15, 38,61,84 共四个,现要将关键字为 49 的元素加到表中,用二次探测法解决冲突,则放入的位置是( )。
A.8 B.3 C.5 D.9
答案:D
解释:关键字 15 放入位置 4,关键字 38 放入位置 5,关键字 61 放入位置 6,关键字 84 放入位置 7,再添加关键字 49,计算得到地址为 5,冲突,用二次探测法解决冲突得到新 地址为 6,仍冲突,再用用二次探测法解决冲突,得到新地址为 4,仍冲突,再用用二次探测法解决冲突,得到新地址为 9,不冲突,即将关键字 49 放入位置 9。
二、折半插入排序算法题
给定一个长度为 10 的整数序列:{12, 2, 16, 30, 28, 10, 16, 20, 6, 18},请你使用 折半插入排序(Binary Insertion Sort) 对该序列进行排序。要求按照折半插入排序的思想:
1、对序列从左到右逐个处理;
2、对当前元素在已排序区间中利用折半查找(二分查找)确定插入位置;
3、最终输出一个从小到大排列的序列。
【输入示例】:
12 2 16 30 28 10 16 20 6 18
【输出示例】:
2 6 10 12 16 16 18 20 28 30
#include <stdio.h>
void BinaryInsertSortWithSentinel(int a[], int n) {
int i, j, low, high, mid, temp;
// 数组 a 的有效数据放在 a[1..n],a[0] 用作哨兵
for (i = 2; i <= n; i++) {
temp = a[i];
// 折半查找在已排序区间 a[1..i-1] 中确定插入位置 low(1..i)
low = 1;
high = i - 1;
while (low <= high) {
mid = (low + high) / 2;
if (a[mid] > temp)
high = mid - 1;
else
low = mid + 1;
}
// 此时 low 为插入位置
// 将待插入元素放入哨兵 a[0]
a[0] = temp;
// 从 i-1 开始向左搬移大于哨兵的元素(哨兵保证不越界)
j = i - 1;
while (j >= low) { // 因为通过折半已算出 low,仍用 low 限制搬移范围
a[j + 1] = a[j];
j--;
}
// 把哨兵(即 temp)放到正确位置
a[low] = a[0];
}
}
int main() {
// 使用 1-based 存放输入,a[0] 保留给哨兵
int a[11] = {0, 12, 2, 16, 30, 28, 10, 16, 20, 6, 18};
int n = 10;
int i;
BinaryInsertSortWithSentinel(a, n);
// 输出排序后的序列(从 a[1] 到 a[n])
for (i = 1; i <= n; i++) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
三、算法题讲解
-
void BinaryInsertSortWithSentinel(int a[], int n) {-
定义一个函数,接收一个整型数组
a和元素个数n。约定数组a使用 1-based 下标 存放实际数据(也就是有效元素在a[1]到a[n]),a[0]用作哨兵(sentinel)。 -
教学点:说明为何使用 1-based(哨兵更直观),以及哨兵的作用是帮助保存临时值、简化边界判断或减少某些比较。
-
-
int i, j, low, high, mid, temp;-
局部变量说明:
-
i:外层循环索引,表示当前要插入的元素位置(从第 2 个元素开始)。 -
j:用于元素后移时的索引(从右向左移动)。 -
low, high, mid:用于在已排序区间做折半(binary)查找,确定插入位置。 -
temp:暂存当前待插入元素的值(将放到哨兵a[0])。
-
-
教学点:讲解每个变量的含义及取值范围(
low在[1, i],high在[1, i-1])。
-
-
for (i = 2; i <= n; i++) {-
从第二个元素开始,对数组中每一个元素执行“插入”操作(因为第 1 个元素
a[1]初始时视为已排序)。 -
教学点:说明插入排序的外层循环为什么从 2 开始,强调“已排序区间”和“待插入元素”的概念。
-
-
temp = a[i];-
把当前要插入的值保存到
temp(暂存),后续将先通过折半找到合适位置,再搬移元素,最后把temp插入。 -
教学点:这样做保留原值,方便后移元素覆盖位置时不会丢失待插入值。
-
-
low = 1;-
折半查找的左界初始化为
1(已排序区间左端)。
-
-
high = i - 1;-
折半查找的右界初始化为
i - 1(已排序区间右端)。
-
-
while (low <= high) {-
标准的二分查找循环,以在
a[low..high]中寻找插入点。 -
教学点:这里的目标不是寻找元素是否存在,而是确定 插入位置(第一个大于
temp的位置),通常二分返回low作为插入位置。
-
-
mid = (low + high) / 2;-
取中间位置
mid。课堂上可指出用(low+high)/2会有溢出风险(在 C 中可以改成low + (high-low)/2),但对于小规模教学数组问题可以直接使用。
-
-
if (a[mid] > temp) -
high = mid - 1; -
else -
low = mid + 1;-
如果中点的值大于
temp,说明插入点在左半区(包含mid),因此把high移到mid - 1;否则插入点在右半区(mid+1..high),把low移到mid + 1。 -
循环结束后,
low就是应该插入的位置(插入到low,使得a[1..i]保持有序)。 -
教学点:强调返回的
low含义 —— 它指向第一个大于temp的位置;如果temp最大,则low == i(即插入到末尾)。
-
-
// 此时 low 为插入位置-
注释说明(课堂可以在板书写出
low的几种典型情形)。
-
-
a[0] = temp;-
把待插入值放入
a[0],作为哨兵(临时存放),这样下面的搬移或比较可以方便引用a[0]。 -
教学点:说明哨兵的优势(有时可避免边界检查或将
temp暂存在数组中便于后续写入)。同时说明这里我们仍然先计算low,搬移循环使用low作为边界(保持逻辑清晰)。
-
-
j = i - 1;-
设置
j从已排序区的右端开始(准备往左搬移),注意j初值为i-1。
-
-
while (j >= low) { -
a[j + 1] = a[j]; -
j--; -
}-
将
a[low..i-1]中的所有元素整体向右移动一位,为在a[low]放置temp腾出空间。 -
终止条件是
j < low(即所有大于等于low的元素都已右移)。 -
教学点:
-
解释为什么从右向左搬移(避免覆盖尚未搬移的元素)。
-
虽然使用了哨兵,但这里我们没有用哨兵来代替
low,而是把low仍作为终止条件,这能保证稳定性并避免过度依赖哨兵的比较方式。 -
若不使用
low,而仅基于while (a[j] > a[0])进行移动,逻辑也可成立,但那是“直接插入 + 哨兵”的变体,不再保留折半查找的明确插入位置信息。
-
-
-
a[low] = a[0];-
把保存在哨兵
a[0]的temp放到正确位置low,插入完成。 -
此时
a[1..i]变为有序。
-
-
}(for 循环结束)-
继续处理下一个
i,直到i == n为止,排序完成。
-
【main 中要点(快速讲解)】
-
int a[11] = {0, 12, 2, 16, 30, 28, 10, 16, 20, 6, 18};-
使用长度为 11 的数组(0..10),其中
a[0]初始置 0(将被当作哨兵使用),输入数据放在a[1]..a[10]。
-
-
BinaryInsertSortWithSentinel(a, n);-
调用排序函数。
-
-
输出循环从
a[1]到a[n]打印排序结果。
四、期末习题复习
下节课再说
五、力扣题
35、搜索插入位置
https://leetcode.cn/problems/search-insert-position/description/
426

被折叠的 条评论
为什么被折叠?



