gesp 2025年12月 五级真题解析
2026/4/28 1:45:36 网站建设 项目流程

GESP C++ 五级 2025 年 12 月真题(仅选择 + 判断)

一、单选题(每题 2 分,共 30 分)

第 1 题

原题:对如下定义的循环单链表,横线处填写( )。

// 循环单链表的结点 struct Node { int data; Node* next; // 数据域 // 指针域 Node(int d) : data(d), next(nullptr) {} }; // 创建一个只有一个结点的循环单链表 Node* createList(int value) { Node* head = new Node(value); head->next = head; return head; } // 在循环单链表尾部插入新结点 void insertTail(Node* head, int value) { Node* p = head; while (p->next != head) { p = p->next; } Node* node = new Node(value); node->next = head; p->next = node; } // 遍历并输出循环单链表 void printList(Node* head) { if (head == nullptr) return; } Node* p = head; _______________________ 在此处填写代码 cout<<endl; }

A. while (p != nullptr) { cout << p->data; p = p->next; }

B. while (p->next != nullptr) { cout << p->data; p = p->next; }

C. do { cout << p->data; p = p->next; } while (p != head);

D. while (p != head) { cout << p->data; p = p->next; }

答案:C解析

  • 循环单链表的特点是尾结点的 next 指向头结点,不存在 nullptr。

  • A/B:循环条件永远不会结束,会陷入死循环。

  • D:第一次循环就不满足条件,直接跳过初始结点,漏输出。

  • C:用 do-while 先执行一次输出,再判断是否回到初始结点,能完整遍历所有结点。


第 2 题

原题:区块链技术是⽐特币的基础。在区块链中,每个区块指向前⼀个区块,构成链式列表,新区块只能接在链 尾,不允许在中间插⼊或删除。下⾯代码实现插⼊区块添加函数,则横线处填写( )。

//区块(节点) struct Block { int index; string data; Block* prev; // 区块编号(高度) // 区块里保存的数据 // 指向前一个区块 Block(int idx, const string& d, Block* p) : index(idx), data(d), prev(p) {} }; // 区块链 struct Blockchain { Block* tail; // 初始化 void init() { tail = new Block(0, "Genesis Block", nullptr); } // 插入新区块 void addBlock(const string& data) { _______________________ } // 释放内存 void clear() { Block* cur = tail; while (cur != nullptr) { Block* p = cur->prev; delete cur; cur = p; } tail = nullptr; } };

A. Block *newBlock = new Block (tail->index+1, newData, nullptr);

tail->next = newBlock;

tail = newBlock;

B. Block *newBlock = new Block (tail->index+1, newData, tail);

tail = newBlock;

C. Block *newBlock = new Block (tail->index, newData, tail);

tail = newBlock;

D. Block *newBlock = new Block (tail->index+1, newData, tail->prev);

tail = newBlock;

答案:B解析

  • 新区块的序号应为tail->index + 1,prev 指针应指向当前尾区块。

  • A:prev 设为 nullptr,链断裂;C:序号错误;D:prev 指向尾的前一个区块,逻辑错误。

  • B 正确:创建新区块并让 tail 指向它,链结构完整。


第 3 题

原题:下⾯关于单链表和双链表的描述中,正确的是( )。

struct DNode { int data; DNode* prev; DNode* next; }; // 在双链表中删除指定节点 void deleteNode(DNode* node) { if (node->prev) { node->prev->next = node->next; } if (node->next) { node->next->prev = node->prev; } delete node; } struct SNode { int data; SNode* next; }; // 在单链表中删除指定节点 void deleteSNode(SNode* head, SNode* node) { SNode* prev = head; while (prev->next != node) { prev = prev->next; } prev->next = node->next; delete node; }

A. O (1) 和 O (1)

B. O (n) 和 O (1)

C. O (1) 和 O (n)

D. O (n) 和 O (n)

答案:C解析

  • 双向链表:结点有 prev 和 next 指针,可直接修改前后结点的指针,时间复杂度 O (1)。

  • 单链表:必须先遍历找到被删结点的前驱结点,才能修改指针,时间复杂度 O (n)。


第 4 题

原题:假设我们有两个数a=38 和b=14 ,它们对模 m同余,即a ≡ b (mod m)。以下哪个值不可能是 ?。 A. 3 B. 4 C. 6 D. 9

答案:D解析

  • 同余的定义:a ≡ b (mod m)等价于m整除a-b

  • 计算:38-14=24,m 必须是 24 的约数。

  • 3、4、6 都是 24 的约数,9 不是,所以 D 不可能。


第 5 题

原题:下面代码实现了欧几里得算法。下面有关说法,错误的是( )。

int gcd1(int a, int b) { return b == 0 ? a : gcd1(b, a % b); } int gcd2(int a, int b) { while (b != 0) { int temp = b; b = a % b; a = temp; } return a; }

A. gcd1 () 实现为递归方式。

B. gcd2 () 实现为迭代方式。

C. 当 a 较大时,gcd1 () 实现会多次调用自身,需要较多额外的辅助空间。

D. 当 a 较大时,gcd1 () 的实现比 gcd2 () 执行效率更高。

答案:D解析

  • A/B 正确:gcd1 递归,gcd2 迭代。

  • C 正确:递归调用会占用栈空间,深度大时开销高。

  • D 错误:递归存在函数调用、栈帧创建 / 销毁的额外开销,迭代版 gcd2 效率更高。


第 6 题

原题:唯⼀分解定理描述的内容是()。

A. 任何正整数都可以表⽰为两个素数的和。

B. 任何⼤于1的合数都可以唯⼀分解为有限个质数的乘积

C. 两个正整数的最⼤公约数总是等于它们的最⼩公倍数除以它们的乘积

D. 所有素数都是奇数。

答案:B解析

  • 算术基本定理的核心:任何大于 1 的正整数,都可以唯一分解为有限个素数的乘积(素因子不计顺序)。

  • A/D:错误,是素数而非合数;C:整数包括 1 和负数,不适用该定理。


第 7 题

原题:下述代码实现素数表的线性筛法,筛选出所有⼩于等于 的素数,则横线上应填的代码是( )。

vector<int> linear_sieve(int n) { vector<bool> is_prime(n +1, true); vector<int> primes; is_prime[0] = is_prime[1] = 0; //0和1两个数特殊处理 for (int i = 2; i <= n; ++i) { if (is_prime[i]) { primes.push_back(i); } ________________________________ { is_prime[ i * primes[j] ] = 0; if (i % primes[j] == 0) break; } } } return primes; }

A.for (int j = 0; j < primes.size() && i * primes[j] <= n; j++)

B.for(int j = sqrt(n); j <= n && i * primes[j] <= n; j++)

C.for (int j = 1; j <= sqrt(n); j++)

D.for(int j = 1; j < n && i * primes[j] <= n; j++)

答案:A解析

  • 线性筛的内层循环需要遍历已找到的素数,同时保证乘积不超过 n。

  • A 正确:从 j=0 开始遍历 primes 数组,同时限制i*primes[j] <= n,符合线性筛逻辑。

  • B/C/D:起始索引错误或循环条件错误,无法正确标记合数。


第 8 题

原题:下列关于排序算法的说法,正确的是( )。

A. 快速排序是稳定的排序算法

B. 归并排序是稳定的排序算法

C. 插⼊排序是不稳定排序

D. . 冒泡排序不是原地排序

答案:B解析

  • 稳定排序的定义:相等元素的相对位置在排序后不变。

  • A 错误:快排是不稳定的;B 正确:归并排序是稳定的;C 错误:直接插入排序是稳定的;D 错误:冒泡排序时间复杂度 O (n²),远不如快排高效。


第 9 题

原题:下面代码实现了归并排序。下述关于归并排序的说法中,不正确的是( )。

void merge(vector<int>& arr, vector<int>& temp, int l, int mid, int r) { int i = l, j = mid + 1, k = l; while (i <= mid && j <= r) { if (arr[i] <= arr[j]) temp[k++] = arr[i++]; else temp[k++] = arr[j++]; } while (i <= mid) temp[k++] = arr[i++]; while (j <= r) temp[k++] = arr[j++]; for (int p = l; p <= r; p++) arr[p] = temp[p]; } void mergeSort(vector<int>& arr, vector<int>& temp, int l, int r) { if (l >= r) return; int mid = l + (r - l) / 2; mergeSort(arr, temp, l, mid); mergeSort(arr, temp, mid + 1, r); merge(arr, temp, l, mid, r); }

A. 归并排序的平均复杂度是 O (nlogn)。

B. 归并排序需要 O (n) 的额外空间。

C. 归并排序在最坏情况的时间复杂度是 O (n²)。

D. 归并排序适合大规模数据。

答案:C解析

  • 归并排序的时间复杂度始终为 O (nlogn),不受数据分布影响,不存在 O (n²) 的情况。

  • A/B/D 的描述均正确。


第 10 题

原题:下述C++代码实现了快速排序算法,最坏情况的时间复杂度是()。

int partition(vector<int>& arr, int low, int high) { int i = low, j = high; int pivot = arr[low]; // 以首元素为基准 while (i < j) { while (i < j && arr[j] >= pivot) j--; while (i < j && arr[i] <= pivot) i++; if (i < j) swap(arr[i], arr[j]); } swap(arr[i], arr[low]); return i; } void quickSort(vector<int>& arr, int low, int high) { if (low >= high) return; int p = partition(arr, low, high); quickSort(arr, low, p - 1); quickSort(arr, p + 1, high); }

A. O(n)

B. O(logn)

C. O()

D. O(nlogn)

答案:C解析

  • 快排的效率依赖于划分的平衡性。当数据已基本有序时,每次划分都会产生极不均衡的子数组,导致时间复杂度退化为 O (n²),失去优势。


第 11 题

原题:下面代码尝试在有序数组中查找第一个大于等于 x 的元素位置。如果没有大于等于 x 的元素,返回 arr.size ()。以下说法正确的是( )。

int lower_bound(vector<int>& arr, int x) { int l = 0, r = arr.size(); while(l < r) { int mid = l + (r - l) / 2; if(arr[mid] >= x) r = mid; else l = mid + 1; } return l; }

A. 上述代码逻辑正确

B. 上述代码逻辑错误,while 循环条件应该用 l <= r

C. 上述代码逻辑错误,mid 计算错误

D. 上述代码逻辑错误,边界条件不对

答案:A解析

  • 这是标准的lower_bound实现,采用左闭右开区间[l, r),循环条件l < r、mid 计算、边界更新均正确,能找到第一个不小于 x 的元素位置,找不到时返回 arr.size ()。


第 12 题

原题:⼩杨要把⼀根长度为 l <= r L 的⽊头切成 K 段,使得每段长度⼩于等于 x。已知每切⼀⼑只能把⼀段⽊头分成 两段,他⽤⼆分法找到满⾜条件的最⼩ x(x 为正整数),则横线处应填写( )。

// 判断:在不超过 K 次切割内,是否能让每段长度 <= x bool check(int L, int K, int x) { int cuts = (L - 1) / x; return cuts <= K; } // 二分查找最小可行的 x int binary_cut(int L, int K) { int l = 1, r = L; while (l < r) { int mid = l + (r - l) / 2; ________________________________ } return l; } int main() { int L = 10; // 木头长度 int K = 2; // 最多切 K 刀 cout << binary_cut(L, K) << endl; return 0; }

A.

if (check(L, K, mid)) r = mid; else l = mid + 1;

B.

if (check(L, K, mid)) r = mid + 1; else l = mid - 1;

C.

if (check(L, K, mid)) r = mid + 1 ; else l = mid + 1;

D.

if (check(L, K, mid)) r = mid+1; else l = mid ;

答案:A解析

  • 二分查找的核心:当当前 mid 长度能切出≥k 段时,说明 L 可以更大,所以r = mid(缩右边界,保留 mid);不能时说明 L 太大,需要减小,l = mid + 1(移左边界)。

  • 对应逻辑:能切出≥k 段 → 尝试更小的 L(向右侧收敛),不能则尝试更大的 L。


第 13 题

原题:下面给出了阶乘计算的两种方式。以下说法正确的是( )。

int factorial1(int n) { if (n <= 1) return 1; return n * factorial1(n - 1); } int factorial2(int n) { int acc = 1; while (n > 1) { acc = n * acc; n = n - 1; } return acc; }

A. 上面两种实现方式的时间复杂度相同,都为 O (n)

B. 上面两种实现方式的空间复杂度相同,都为 O (n)

C. 上面两种实现方式的空间复杂度相同,都为 O (1)

D. 函数 factorial1 () 的时间复杂度为 O (2ⁿ),函数 factorial2 () 的时间复杂度为 O (n)

答案:A解析

  • factorial1(递归):调用 n 次,时间复杂度 O (n),空间复杂度 O (n)(栈开销)。

  • factorial2(迭代):循环 n-1 次,时间复杂度 O (n),空间复杂度 O (1)。

  • 两者时间复杂度均为 O (n),A 正确,B/C/D 错误。


第 14 题

原题:给定有 个任务,每个任务有截⽌时间和利润,每个任务耗时 1 个时间单位、必须在截⽌时间前完成,且每 个时间槽最多做 1 个任务。为了在规定时间内获得最⼤利润,可以采⽤贪⼼策略,即按利润从⾼到低排序,尽量安 排,则横线处应填写( )

struct Task { int deadline; //截止时间 int profit; //利润 }; void sortByProfit(vector<Task>& tasks) { sort(tasks.begin(), tasks.end(), [](const Task& a, const Task& b) { return a.profit > b.profit; }); } int maxProfit(vector<Task>& tasks) { sortByProfit(tasks); int maxTime = 0; for (auto& t : tasks) { maxTime = max(maxTime, t.deadline); } vector<bool> slot(maxTime + 1, false); int totalProfit = 0; for (auto& task : tasks) { for (int t = task.deadline; t >= 1; t--) { if (!slot[t]) { _______________________ //在此处填入代码 break; } } } return totalProfit; }

A、lot[t] = true; totalProfit += task.profit;

B、slot[t] = false; totalProfit += task.profit;

C、slot[t] = true; totalProfit = task.profit;

D、slot[t] = false; totalProfit = task.profit;

答案:A解析

  • 贪心算法的核心:按利润从高到低安排任务,找到任务截止时间前的最晚空闲时间槽,标记为已占用,并将该任务的利润计入总利润。


第 15 题

原题:下⾯代码实现了对两个数组表⽰的正整数的⾼精度加法(数组低位在前),则横线上应填写( )。

vector<int> add(vector<int> a, vector<int> b) { vector<int> c; int carry = 0; for (int i = 0; i < a.size() || i < b.size(); i++) { if (i < a.size()) carry += a[i]; if (i < b.size()) carry += b[i]; _______________________ //在此处填入代码 } if (carry) c.push_back(carry); return c; }

A. c.push_back(carry / 10); carry %= 10;

B. c.push_back(carry % 10); carry /= 10;

C. c.push_back(carry % 10);

D. c.push_back(carry); carry /= 10;

答案:B解析

  • 高精度加法规则:当前位的数字为sum % 10,进位为sum / 10,carry 用于下一位的计算。


二、判断题(每题 2 分,共 20 分)

第 1 题

原题:数组和链表都是线性表。链表的优点是插⼊删除不需要移动元素,并且能随机查找( )

答案:×解析:链表不支持随机访问,访问第 i 个元素需要遍历到该位置,时间复杂度 O (n)。


第 2 题

原题:假设函数 gcd() 函数能正确求两个正整数的最⼤公约数,则下⾯的 lcm(a,b) 函数能正确找到两个正整 数 a 和 b 的最⼩公倍数。( )

int lcm(int a, int b) { return a / gcd(a, b) * b;}

答案:√解析:最小公倍数就等于a*b/gcd(a,b)。


第 3 题

原题:在单链表中,已知指针 覆盖 p 的值与 p 指向要删除的结点(⾮尾结点),想在 删除 p,可⾏做法是⽤ next ,然后删除 p->next 。( )

答案:√解析:在单链表中,已知指针p指向要删除的结点(非尾结点),想在 \(O(1)\) 时间内删除它,常规方法是先找到p的前驱结点,修改前驱的next指针,再删除p,但这个过程需要遍历链表,时间复杂度为 \(O(n)\)。


第 4 题

原题:在求解所有不大于 n 的素数时,线性筛法(欧拉筛)都应当优先于埃氏筛法使用,因为线性筛法的时间复杂度为 O (n),低于埃氏筛法的 O (nloglogn)。( )

答案:×解析:该说法过于绝对。线性筛理论复杂度更低,但常数开销更大,在中小规模数据下,埃氏筛的实际运行效率可能更高,且代码更简洁易写,并非 “都应当优先使用”。


第 5 题

原题:⼆分查找仅适⽤于有序数据。若输⼊数据⽆序,当仅进⾏⼀次查找时,为了使⽤⼆分⽽排序通常不划算。( )

答案:√


第 6 题

原题:通过在数组的第⼀个、最中间和最后⼀个这3个数据中选择中间值作为枢轴(⽐较基准),快速排序算法可 降低落⼊最坏情况的概率。( )

答案:√解析


第 7 题

原题:贪⼼算法在每⼀步都做出当前看来最优的局部选择,并且⼀旦做出选择就不再回溯;⽽分治算法将问题分解 为若⼲⼦问题分别求解,再将⼦问题的解合并得到原问题的解。。( )

答案:√解析


第 8 题

原题:以下 fib 函数计算第 n 项斐波那契数(fib(0)=0, fib(1)=1),其时间复杂度为O(n) 。( )

int fib(int n) { if (n <= 1) return n; return fib(n-1) + fib(n-2); }

答案:×解析


第 9 题

原题:递归函数⼀定要有终⽌条件,否则可能会造成栈溢出。( )

答案:√解析


第 10 题

原题:使⽤贪⼼算法解决问题时,通过对每⼀步求局部最优解,最终⼀定能找到全局最优解。( )

答案:×解析


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询