楼主: 昱君
704 0

[其他] 数据结构实验十一:插入排序+期末复习 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

小学生

42%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
50 点
帖子
4
精华
0
在线时间
0 小时
注册时间
2018-12-7
最后登录
2018-12-7

楼主
昱君 发表于 2025-12-3 22:35:08 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

本节课实验课的主要内容安排如下:

  • 讲解上一节布置的3道课后习题
  • 设计一道算法题:折半插入排序
  • 布置一道力扣题目
  • 安排下次课后的学习任务:完成学习通中的“期末习题复习一”

一、第七章课后习题解析

【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 个元素,初始 low=1,high=10。
第一次取 mid = (1+10)/2 = 5,与第5个元素 20 比较,58 > 20,因此在右半区间继续查找(low=6)。
第二次取 mid = (6+10)/2 = 8,与第8个元素 70 比较,58 < 70,转向左半区间(high=7)。
第三次取 mid = (6+7)/2 = 6,与第6个元素 30 比较,58 > 30,继续在右半部分(low=7)。
第四次取 mid = (7+7)/2 = 7,与第7个元素 50 比较,58 > 50,但下一位置已超出范围,查找失败。
因此比较顺序为:20 → 70 → 30 → 50,对应选项 A。

【第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

解析: 此题考察的是二叉排序树(BST)的构建规则及不同插入顺序对树形的影响。
根据 BST 性质:对于任意节点,其左子树所有节点值小于它,右子树所有节点值大于它。

分析各序列构建过程:

序列 A:
插入 100 作为根;
插入 80 → 100 的左孩子;
插入 90 → 大于 80 小于 100,成为 80 的右孩子;
插入 60 → 小于 80,成为 80 的左孩子;
插入 120 → 大于 100,成为 100 的右孩子;
插入 110 → 小于 120,成为 120 的左孩子;
插入 130 → 大于 120,成为 120 的右孩子。

A 构造的树结构为:

100
       /   \
     80    120
    /  \   / \
   60  90 110 130

序列 B:
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 → 100 左孩子(关键差异点);
80 → 60 右孩子;
90 → 80 右孩子;
120 → 100 右孩子;
110 和 130 分别为 120 的左右孩子。

C 的树结构:

100
       /   \
     60     120
       \    / \
       80 110 130
         \
         90

明显与 A、B 不同,左子树形态发生变化。

序列 D:
构建顺序虽异,但最终结构与 B 相同:
100 为根;
80 为其左孩子;
60 和 90 分别为 80 的左右孩子;
120 为 100 右孩子;
130 为 120 右孩子;
110 为 120 左孩子。

D 的树结构:

100
       /   \
     80     120
    /  \    / \
   60  90 110 130

综上所述,只有 C 序列生成的树与其他三项不等价,故正确答案为 C。

【第14题】
设哈希表长度为 14,哈希函数为 H(key) = key % 11。当前表中已有关键字:15、38、61、84。现需插入关键字 49,采用二次探测法解决冲突,问其应存入的位置是?

选项:
A. 8    B. 3    C. 5    D. 9

答案:D

解析:
计算各关键字的哈希地址:
H(15) = 15 % 11 = 4 → 存入位置 4
H(38) = 38 % 11 = 5 → 存入位置 5
H(61) = 61 % 11 = 6 → 存入位置 6
H(84) = 84 % 11 = 7 → 存入位置 7
现插入 49:H(49) = 49 % 11 = 5,发生冲突(位置 5 已被占用)

使用二次探测法处理冲突,探测序列为:
d = (H(key) + i) % m 或 (H(key) - i) % m(通常先正向尝试)
第一次冲突后,i = 1:
(5 + 1) % 14 = 6 → 仍冲突(61 占用)
i = 2:
(5 + 2) % 14 = 9 → 位置 9 为空,可插入。

因此关键字 49 被放入位置 9,答案为 D。

二、折半插入排序算法实践

给定一个长度为 10 的整数数组:
{12, 2, 16, 30, 28, 10, 16, 20, 6, 18}

要求使用折半插入排序(Binary Insertion Sort)对其进行升序排列。具体步骤如下:

  1. 从第二个元素开始,逐个处理每个元素;
  2. 对于当前元素,在前面已排序的部分中使用折半查找确定其应插入的位置;
  3. 将该位置后的元素整体后移一位,腾出空间;
  4. <4>插入当前元素。

通过不断扩展有序区,最终得到完整排序序列。

输入示例:
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) {
    // 函数接收一个整型数组和元素个数
    // 数组采用 1-based 下标存储有效数据(即实际数据从下标 1 到 n)
    // a[0] 作为哨兵单元,用于暂存数据并简化边界处理

    教学点:使用 1-based 索引可以让哨兵机制更直观。哨兵的主要作用是临时保存待插入值,避免每次移动时重复传参,并减少对数组边界的显式判断。

    int i, j, low, high, mid;
    // 局部变量说明:
    // i:外层循环控制变量,表示当前要处理的元素位置,从第 2 个开始(i=2)
    // j:在元素后移过程中使用的索引,方向为从右向左
    // low 和 high:折半查找的左右边界指针
    // mid:二分过程中的中点位置
    // a[0]:用作哨兵,存放当前待插入的元素值

    教学点:讲解各变量含义及其取值范围:
    // i 的范围是 [2, n]
    // j 初始为 i-1,逐步递减至 low
    // low 起始为 1,high 起始为 i-1,在查找过程中动态调整

    for (i = 2; i <= n; i++) {
        // 外层循环从第二个元素开始遍历
        // 第一个元素 a[1] 被视为已排序部分的起始

        教学点:解释为何外层循环从 2 开始:
        // 插入排序的基本思想是维护一个有序区间,初始时 a[1] 自身为有序
        // 每次将未排序区的第一个元素插入到有序区的合适位置
        // 因此需从第 2 个元素开始执行插入操作

        a[0] = a[i]; 
        // 将当前待插入元素 a[i] 存入哨兵位置 a[0]
        // 这样可以在后续比较和搬移中直接引用 a[0],无需额外变量暂存

        教学点:强调该步骤的意义:
        // 防止在右移过程中原值被覆盖而丢失
        // 同时可利用 a[0] 参与比较,有时可省略边界检查(但在本实现中仍保留明确边界)

        low = 1;     
        // 折半查找的左边界初始化为有序区间的起始位置(下标 1)

        high = i - 1; 
        // 右边界为当前已排序部分的末尾(即 i-1)

        
a
while (low <= high) { // 执行标准的二分查找结构,目标不是找相等元素 // 而是确定“第一个大于 a[0] 的位置”,即插入点 教学点:说明查找目标: // 返回的位置应满足:插入后整个序列仍然有序 // 最终插入位置由 low 给出(循环结束时 low == high + 1) mid = (low + high) / 2; // 计算中间位置
n
教学点:提醒潜在问题: // 在 C 语言中,(low + high) 可能发生整数溢出 // 更安全写法为 low + ((high - low) / 2) // 但教学场景中若数组较小,可暂时忽略此风险 if (a[mid] > a[0]) { // 若中间元素大于待插入值,说明插入点在左半段(含 mid) high = mid - 1; } else { // 否则插入点在右半段(不包含 mid) low = mid + 1; }
a[1]
a[n]
a[0]
int i, j, low, high, mid, temp;
i
j
low, high, mid
temp
a[0]
low
[1, i]
high
[1, i-1]
temp = a[i];
temp
low = 1;
1
high = i - 1;
i - 1
a[low..high]
temp
mid = (low + high) / 2;
mid
(low+high)/2
low + (high-low)/2
if (a[mid] > temp)
high = mid - 1;
else
low = mid + 1;
mid - 1
mid+1..high
mid + 1
a[1..i]
low == i
// 此时 low 为插入位置
a[0] = temp;
j = i - 1;
j
i-1
a[j + 1] = a[j];
j--;
}
a[low..i-1]
a[low]
j < low
low
while (a[j] > a[0])
a[low] = a[0];
// 循环结束后,low 即为正确的插入位置 // 它指向第一个大于 a[0] 的元素所在位置 // 如果所有元素都不大于 a[0],则 low = i(插入末尾) } // 此时 low 已确定为插入位置 // 接下来需要将 [low, i-1] 区间内的元素整体右移一位 j = i - 1; // 设置搬移起始位置:从已排序区最后一个元素开始向右移动
i
while (j >= low) { // 从右向左依次移动元素,直到腾出位置 low a[j + 1] = a[j]; j--; }
i == n
教学点:解释搬移方向的重要性: // 必须从右往左移动,否则会覆盖尚未处理的数据 // 例如先移动左边会导致右边原始值丢失 // 虽然 a[0] 中已有待插入值,但此处没有依赖哨兵进行条件判断 // 而是以计算出的 low 作为搬移终点,保持逻辑清晰稳定 a[low] = a[0]; // 将哨兵中保存的原 a[i] 值插入到正确位置 low
【main
// 至此,a[1..i] 已恢复有序状态 // 继续下一轮循环处理下一个元素 } // 整个数组 a[1..n] 已完成排序 // 排序结束 教学要点总结(快速回顾): // 1. 使用 1-based 数组便于理解哨兵机制 // 2. 哨兵 a[0] 用于暂存当前值,防止丢失 // 3. 折半查找确定插入位置,提高查找效率(O(log i)) // 4. 元素右移腾位,必须从右向左进行 // 5. 最终通过 low 定位插入点并将 a[0] 写回 // 6. 总体时间复杂度仍为 O(n),但比较次数优化至约 O(n log n)
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/

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:期末复习 数据结构 Description sentinel Problems
相关内容:数据结构期末复习

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
加好友,备注cda
拉您进交流群
GMT+8, 2026-2-13 10:00