GESP 客观题评测系统

2025-03-Level-5

2025-03-Level-5

试卷解析总览,可直接查看每题答案与解析。

单选题(每题 2 分)

1 题(单选题

链表不具备的特点是()。

A.
可随机访问任何一个元素
B.
插入、删除操作不需要移动元素
C.
无需事先估计存储空间大小
D.
所需存储空间与存储元素个数成正比

正确答案A

解析详情

【答案】A

【考点】链表的基本特性

【解析】 链表是一种链式存储结构,访问元素需要从头节点开始逐个遍历,不支持随机访问。插入和删除只需修改指针,不需要移动元素。

【易错点】 混淆链表和数组的访问特性。

2 题(单选题

双向链表中每个结点有两个指针域 prev 和 next,分别指向该结点的前驱及后继结点。设 p 指向链表中的一个结点,它的前驱结点和后继结点均非空。要删除结点 p,则下述语句中错误的是()。

A.
p->next->prev = p->next;
p->prev->next = p->prev;
delete p;
B.
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
C.
p->next->prev = p->prev;
p->next->prev->next = p->next;
delete p;
D.
p->prev->next = p->next;
p->prev->next->prev = p->prev;
delete p;

正确答案A

解析详情

【答案】A

【考点】双向链表的删除操作

【解析】 删除节点 p 时,应让 p 的前驱指向 p 的后继,p 的后继指向 p 的前驱。选项 A 中 p->next->prev = p->next 让后继的前驱指向了其自身,逻辑错误。

【易错点】 指针指向对象错误,导致链表断裂或形成自环。

3 题(单选题

假设双向循环链表包含头尾哨兵结点(不存储实际内容),分别为 head 和 tail,链表中每个结点有两个指针域 prev 和 next,分别指向该结点的前驱及后继结点。下面代码实现了一个空的双向循环链表,横线上应填的最佳代码是()。

// 链表结点

template <typename T>
struct ListNode {
    T data;
    ListNode* prev;
    ListNode* next;

    // 构造函数
    explicit ListNode(const T& val = T())
        : data(val), prev(nullptr), next(nullptr) {
    };

    struct LinkedList {
        ListNode<T>* head;
        ListNode<T>* tail;
    };

    void InitLinkedList(LinkedList* list) {
        list->head = new ListNode<T>;
        list->tail = new ListNode<T>;
        // 在此处填入代码
    }
}
A.
list->head->prev = list->head;
list->tail->prev = list->head;
B.
list->head->next = list->tail;
list->tail->prev = list->head;
C.
list->head->next = list->tail;
list->tail->next = list->head;
D.
list->head->next = list->tail;
list->tail->next = nullptr;

正确答案B

解析详情

【答案】B

【考点】双向链表的初始化

【解析】 在带有头尾哨兵的双向链表中,初始化空表时应建立 head 和 tail 的双向连接。即 head->next 指向 tail,且 tail->prev 指向 head。

【易错点】 忽略双向链表需要同时维护两个方向的指针。

4 题(单选题

用以下辗转相除法(欧几里得算法)求gcd(84, 60)的步骤中,第二步计算的数是()。

int gcd(int a, int b) {
    int big = a > b ? a : b;
    int small = a < b ? a : b;
    if (big % small == 0) {
        return small;
    }
    return gcd(small, big % small);
}
A.
84和60
B.
60和24
C.
24和12
D.
12和0

正确答案B

解析详情

【答案】B

【考点】辗转相除法(欧几里得算法)

【解析】 第一步计算 gcd(84, 60),因为 84 % 60 = 24,函数递归调用 gcd(60, 24)。因此第二步计算的两个数是 60 和 24。

【易错点】 混淆递归调用的参数顺序或步骤编号。

5 题(单选题

根据唯一分解定理,下面整数的唯一分解是正确的()。

A.
18=3×618 = 3 \times 6
B.
28=4×728 = 4 \times 7
C.
36=2×3×636 = 2 \times 3 \times 6
D.
30=2×3×530 = 2 \times 3 \times 5

正确答案D

解析详情

【答案】D

【考点】唯一分解定理

【解析】 唯一分解定理要求将合数分解为若干个质因数的乘积。选项 A 中的 6、B 中的 4、C 中的 6 都不是质数。只有 D 项 30 = 2 × 3 × 5 且所有因数均为质数。

【易错点】 忽视了“唯一分解”必须分解到质数为止的要求。

6 题(单选题

下述代码实现素数表的线性筛法,筛选出所有小于等于n的素数,横线上应填的最佳代码是()。

vector<int> sieve_linear(int n) {
    vector<bool> is_prime(n + 1, true);
    vector<int> primes;
    if (n < 2) return primes;
    is_prime[0] = is_prime[1] = false;
    for (int i = 2; i <= n / 2; i++) {
        if (is_prime[i])
            primes.push_back(i);
        for (int j = 0; j < primes.size() && i * primes[j] <= n; j++) { // 在此处填入代码
            is_prime[i * primes[j]] = false;
            if (i % primes[j] == 0)
                break;
        }
    }
    for (int i = n / 2 + 1; i <= n; i++) {
        if (is_prime[i])
            primes.push_back(i);
    }
    return primes;
}
A.
j < primes.size()
B.
i * primes[j] <= n
C.
j < primes.size() && i * primes[j] <= n
D.
j <= n

正确答案C

解析详情

【答案】C

【考点】线性筛(欧拉筛)实现

【解析】 在线性筛的内层循环中,必须保证访问 primes 数组不越界(j < primes.size()),同时要保证筛选的合数在目标范围内(i * primes[j] <= n)。选项 C 完整描述了这两个条件。

【易错点】 遗漏下标边界检查或筛选数值范围检查。

7 题(单选题

在程序运行过程中,如果递归调用的层数过多,会因为()引发错误。

A.
系统分配的栈空间溢出
B.
系统分配的堆空间溢出
C.
系统分配的队列空间溢出
D.
系统分配的链表空间溢出

正确答案A

解析详情

【答案】A

【考点】递归与系统栈空间

【解析】 函数递归调用时,每一层调用都会在系统的调用栈(Stack)上分配存储空间。如果递归过深,消耗的空间超过了系统分配的限制,就会引发栈溢出错误。

【易错点】 混淆栈(Stack)和堆(Heap)的用途。

8 题(单选题

对下面两个函数,说法错误的是()。

int factorialA(int n) {
    if (n <= 1) return 1;
    return n * factorialA(n-1);
}
int factorialB(int n) {
    if (n <= 1) return 1;
    int res = 1;
    for (int i = 2; i <= n; i++)
        res *= i;
}
A.
两个函数的实现的功能相同。
B.
两个函数的时间复杂度均为O(n)O(n)
C.
factorialA采用递归方式。
D.
factorialB采用递归方式。

正确答案D

解析详情

【答案】D

【考点】递归与迭代的区别

【解析】 factorialA 通过调用函数自身实现,是典型的递归方式;factorialB 通过 for 循环实现,是典型的迭代方式。选项 D 的说法是错误的。

【易错点】 未能正确区分循环(迭代)和函数自调用(递归)。

9 题(单选题

下算法中,()是不稳定的排序。

A.
选择排序
B.
插入排序
C.
归并排序
D.
冒泡排序

正确答案A

解析详情

【答案】A

【考点】排序算法稳定性

【解析】 在选择排序中,由于会发生长距离的元素交换,可能会改变相同元素的相对顺序,因此是不稳定的。冒泡、插入和归并排序通常实现为稳定的排序。

【易错点】 记错常见排序算法(冒泡、插入、选择、快速、归并等)的稳定性特征。

10 题(单选题

考虑以下C++代码实现的快速排序算法,将数据从小到大排序,则横线上应填的最佳代码是()。

int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high]; // 基准值
    int i = low - 1;

    for (int j = low; j < high; j++) {
        // 在此处填入代码
    }
    swap(arr[i + 1], arr[high]);
    return i + 1;
}
// 快速排序
void quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}
A.
if (arr[j] > pivot) {
    i++;
    swap(arr[i], arr[j]);
}
B.
if (arr[j] < pivot) {
    i++;
    swap(arr[i], arr[j]);
}
C.
if (arr[j] < pivot) {
    swap(arr[i], arr[j]);
    i++;
}
D.
if (arr[j] == pivot) {
    i++;
    swap(arr[i], arr[j]);
}

正确答案B

解析详情

【答案】B

【考点】快速排序的 Partition 实现

【解析】 在单向扫描的 Partition 算法中,当遇到小于基准值 pivot 的元素 arr[j] 时,应先移动较小元素区域的边界 i,然后交换 arr[i] 和 arr[j]。选项 B 符合该逻辑。

【易错点】 混淆指针移动和交换的顺序,或判断条件方向写反。

11 题(单选题

若用二分法在[1,100][1,100]内猜数,最多需要猜()次。

A.
100
B.
10
C.
7
D.
5

正确答案C

解析详情

【答案】C

【考点】二分查找的时间复杂度

【解析】 二分查找在长度为 n 的区间中查找,最坏情况下的比较次数为 ceil(log2(n))。对于 n=100,log2(100) 约等于 6.64,向上取整为 7 次。

【易错点】 误记对数底数或计算对数值时估值错误。

12 题(单选题

下面代码实现了二分查找算法,在数组 arr 找到目标元素 target 的位置,则横线上能填写的最佳代码是()。

int binarySearch(int arr[], int left, int right, int target) {
    while (left <= right) {
        // 在此处填入代码
        int mid = left + (right - left) / 2;
        if (arr[mid] == target)
            return mid;
        else if (arr[mid] < target)
            left = mid + 1;
        else
            right = mid - 1;
    }
    return -1;
}
A.
int mid = left + (right - left) / 2;
B.
int mid = left;
C.
int mid = (left + right) / 2;
D.
int mid = right;

正确答案A

解析详情

【答案】A

【考点】二分查找的实现细节

【解析】 在计算中间位置 mid 时,使用 left + (right - left) / 2 可以防止当 left 和 right 均很大时,直接相加导致的整数溢出问题。

【易错点】 忽略 (left + right) 在大数值下可能产生的溢出风险。

13 题(单选题

贪心算法的核心特征是()。

A.
总是选择当前最优解
B.
回溯尝试所有可能
C.
分阶段解决子问题
D.
总能找到最优解

正确答案A

解析详情

【答案】A

【考点】贪心算法概念

【解析】 贪心算法的基本特征是在解决问题的每一步中,都选择当前状态下的最优解,希望通过局部最优选择达到全局最优结果。

【易错点】 误认为贪心算法一定能找到全局最优解,或者将其与分治法混淆。

14 题(单选题

函数 int findMax(int arr[], int low, int high) 计算数组中最大元素,其中数组 arr 从索引 low 到 high,()正确实现了分治逻辑。

A.
if (low == high)
    return arr[low];
int mid = (low + high) / 2;
return arr[mid];
B.
if (low >= high)
    return arr[low];
int mid = (low + high) / 2;
int leftMax = findMax(arr, low, mid - 1);
int rightMax = findMax(arr, mid, high);
return leftMax + rightMax;
C.
if (low > high)
    return 0;

int mid = low + (high - low) / 2;
int leftMax = findMax(arr, low, mid);
int rightMax = findMax(arr, mid + 1, high);
return leftMax * rightMax;
D.
if (low == high)
    return arr[low];

int mid = low + (high - low) / 2;
int leftMax = findMax(arr, low, mid);
int rightMax = findMax(arr, mid + 1, high);
return (leftMax > rightMax) ? leftMax : rightMax;

正确答案D

解析详情

【答案】D

【考点】分治算法应用

【解析】 分治法寻找最大值应将区间分为两半,分别递归求解左半部分和右半部分的最大值,最后通过比较得出两者中的较大者作为结果。选项 D 完整体现了分解、解决和合并的过程。

【易错点】 递归基准条件设置不当,或合并子问题结果的逻辑错误。

15 题(单选题

小杨编写了一个如下的高精度乘法函数,则横线上应填写的代码为()。

vector<int> multiply(vector<int>& a, vector<int>& b) {
    int m = a.size(), n = b.size();
    vector<int> c(m + n, 0);

    // 逐位相乘,逆序存储
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            c[i + j] += a[i] * b[j];
        }
    }

    // 处理进位
    int carry = 0;
    for (int k = 0; k < c.size(); ++k) {
        // 在此处填入代码
        c[k] = temp % 10;
        carry = temp / 10;
    }

    while (c.size() > 1 && c.back() == 0)
        c.pop_back();
    return c;
}
A.
int temp = c[k];
B.
int temp = c[k] + carry;
C.
int temp = c[k] - carry;
D.
int temp = c[k] * carry;

正确答案B

解析详情

【答案】B

【考点】高精度算法进位处理

【解析】 在高精度进位处理中,当前位的最终数值 temp 应该是该位原有的累加值 c[k] 加上来自低位的进位 carry。因此填入 temp = c[k] + carry。

【易错点】 处理进位时忘记加上来自低位的进位值。

判断题(每题 2 分)

1 题(判断题

要删除单链表中某个结点`p`(非尾结点),但不知道头结点,可行的操作是将`p->next`的数据拷贝到`p`的数据部分,将`p->next`设置为`p->next->next`,然后删除`p->next`。

正确答案正确

解析详情

【答案】正确

【考点】单链表节点的删除技巧

【解析】 在不知道头节点的情况下,无法直接通过前驱节点删除 p。通过将后继节点的数据复制给 p,并删除原后继节点,可以在逻辑上达到删除节点 p 的效果。

【易错点】 误认为删除链表节点必须持有前驱节点的指针。

2 题(判断题

链表存储线性表时要求内存中可用存储单元地址是连续的。

正确答案错误

解析详情

【答案】错误

【考点】链表存储结构特点

【解析】 链表通过指针连接各个节点,在物理内存中可以存储在不连续的地址空间中,这是链表与数组(顺序存储)的核心区别。

【易错点】 混淆链表与数组在内存分配上的物理特性。

3 题(判断题

线性筛相对于埃拉托斯特尼筛法,每个合数只会被它的最小质因数筛去一次,因此效率更高。

正确答案正确

解析详情

【答案】正确

【考点】素数筛选算法性能

【解析】 线性筛(欧拉筛)通过逻辑控制保证每个合数只被其最小质因数筛选一次,避免了埃氏筛中同一个数被多次重复筛选的问题,时间复杂度为线性的 O(n)。

【易错点】 不理解线性筛相比埃氏筛如何实现“每个合数只筛一次”。

4 题(判断题

贪心算法通过每一步选择当前最优解,从而一定能获得全局最优解。

正确答案错误

解析详情

【答案】错误

【考点】贪心算法的局限性

【解析】 贪心算法只能保证每一步是局部最优的。虽然在某些特定问题上局部最优能导向全局最优,但对于许多问题,贪心策略并不能获得最终的最优解。

【易错点】 混淆局部最优选择与全局最优结果之间的关系。

5 题(判断题

递归函数必须具有一个终止条件,以防止无限递归。

正确答案正确

解析详情

【答案】正确

【考点】递归的基本结构

【解析】 递归函数必须包含基准情况(终止条件),否则程序会陷入无限自调用,最终导致栈溢出(Stack Overflow)。

【易错点】 在编写递归程序时遗漏或错误设置边界退出条件。

6 题(判断题

快速排序算法的时间复杂度与输入是否有序无关,始终稳定为O(nlogn)O(n\log n)

正确答案错误

解析详情

【答案】错误

【考点】快速排序复杂度分析

【解析】 快速排序的性能高度依赖于基准值的选择。在最坏情况下(如输入已有序且选择边界作为基准),复杂度会退化到 O(n²)。只有平均情况和最好情况是 O(n log n)。

【易错点】 认为所有分治排序算法(如归并 and 快排)在各种情况下性能都保持恒定。

7 题(判断题

归并排序算法的时间复杂度与输入是否有序无关,始终稳定为O(nlogn)O(n \log n)

正确答案正确

解析详情

【答案】正确

【考点】归并排序复杂度稳定性

【解析】 归并排序通过严格的二分区间和线性合并,无论原始数组的有序程度如何,其比较和移动次数始终保持在 O(n log n) 级别。

【易错点】 未能区分归并排序与快速排序在最坏情况下的不同表现。

8 题(判断题

二分查找适用于对无序数组和有序数组的查找。

正确答案错误

解析详情

【答案】错误

【考点】二分查找前提条件

【解析】 二分查找的核心逻辑是基于数据的有序性来排除一半的查找范围。如果数组是无序的,则无法判断目标值在中间位置的哪一侧,因此不适用。

【易错点】 认为只要是查找操作都可以应用高效的二分查找,忽略了数据预处理的要求。

9 题(判断题

小杨有100元去超市买东西,每个商品有各自的价格,每种商品只能买1个,小杨的目标是买到最多数量的商品。小杨采用的策略是每次挑价格最低的商品买,这体现了分治思想。

正确答案错误

解析详情

【答案】错误

【考点】算法策略辨析

【解析】 “每次挑选当前价格最低的商品”属于典型的贪心策略(总是选择当前看起来最优的选项),而非分治思想。

【易错点】 未能准确识别问题场景对应的算法思想分类。

10 题(判断题

归并排序算法体现了分治算法,每次将大的待排序数组分成大小大致相等的两个小数组,然后分别对两个小数组进行排序,最后对排好序的两个小数组合并成有序数组。

正确答案正确

解析详情

【答案】正确

【考点】分治算法在归并排序中的体现

【解析】 归并排序的过程严格遵循了分治法的三个步骤:分解(分为左右子数组)、解决(递归排序子数组)和合并(将有序子数组合并为整体有序数组)。

【易错点】 不理解分治思想在具体经典排序算法中的应用细节。