🎯 GESP C++ 五级真题分析与重要知识点速通
📅 资料更新日期:2026年3月📚 适用对象:准备编程考级五级的学员💡 核心目标:掌握高频考点 · 规避易错陷阱 · 提升解题效率
📐 一、知识点详解与真题示例
🔢 1. 初等数论
📖 知识点详解
初等数论是五级考试的核心基础模块,主要考查以下五大核心内容:
| 素数判定 | [2, √n] 是否有因子 | O(√n) | |
| 质因数分解 | n = p₁^a₁ × p₂^a₂ × ... | O(√n) | |
| 最大公约数(GCD) | gcd(a,b) = gcd(b, a%b) | O(log n) | |
| 最小公倍数(LCM) | lcm(a,b) = a × b / gcd(a,b) | O(log n) | |
| 素数筛法 | O(n log log n) / 线性筛 O(n) | O(n) |
⚠️ 关键技巧:
• ✅ 素数判定循环条件务必写成 i * i <= n,避免i < n导致超时• ✅ 线性筛核心: if (i % primes[j] == 0) break;保证每个合数只被最小质因子筛掉• ✅ 模运算性质: (a + b) % m = ((a % m) + (b % m)) % m,用于大数防溢出
🎯 考查方式与易错点
📝 真题示例
1️⃣ 素数判定代码正误分析(2024年3月单选题第9题)
🔍 题目:下面的代码片段用于判断一个正整数是否为素数。请对以下代码进行修改,使其能正确实现相应功能。()
boolisPrime(int num){if (num < 2) {returnfalse; }for (int i = 2; i * i < num; ++i) { // ⚠️ 问题在此if (num % i == 0) {returnfalse; } }returntrue;}A. 将循环条件改为 i <= sqrt(num)B. 将循环条件改为 i <= num / 2C. 将循环条件改为 i * i <= numD. 以上都不对✅ 【答案】C
🔎 【解析】判断素数时,只需检查从 2 到 √num 是否有因子。原代码循环条件为 i * i < num,会漏判完全平方数的情况(如 9 = 3×3,当 i=3 时 3*3 < 9 不成立,循环提前结束)。修改为 i * i <= num 后,可正确覆盖所有情况。
💡 拓展:
i * i <= num比i <= sqrt(num)更优,避免浮点运算误差和重复开方开销。
❌ 易错点分析:考生可能忽略完全平方数的情况,误认为 i * i < num 足够;或混淆 sqrt() 的返回值类型为 double,导致精度问题。
2️⃣ 线性筛法代码填空(2025年3月单选题第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; 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; } }return primes;}A. for (int j = 0; j < primes.size() && i * primes[j] <= n; j++)B. for (int j = 0; j <= sqrt(n) && i * primes[j] <= n; j++)C. for (int j = 0; j <= n; j++)D. for (int j = 1; j <= sqrt(n); j++)✅ 【答案】A
🔎 【解析】线性筛法的内层循环需要满足两个条件:
1. j < primes.size():确保不越界访问素数列表2. i * primes[j] <= n:确保标记的合数在范围内
🎯 核心原理:当
i % primes[j] == 0时,primes[j]是i的最小质因子,此时i * primes[k](k>j)的最小质因子也是primes[j],应留给i' = i * primes[k] / primes[j]时再筛,避免重复。
❌ 易错点分析:选项B错误地使用 sqrt(n) 作为循环上限,这不符合线性筛的逻辑;选项C和D的条件设置也不正确,可能导致越界或漏筛。
3️⃣ 进制转换(模运算应用)(2023年12月判断题第7题)
🔍 题目:下面的 C++ 代码能实现十进制正整数 N 转换为八进制并输出。()
char s[10];intmain(){int N; cin >> N; string rst = "";while (N != 0) { s[0] = N % 8 + '0'; rst += string(s); N /= 8; } cout << rst << endl;return0;}✅ 【答案】错误
🔎 【解析】进制转换时,先得到的余数是低位,后得到的是高位。该代码将余数直接拼接,输出的是八进制数的逆序。例如 N=10:
10 % 8 = 2 → rst="2"10 / 8 = 11 % 8 = 1 → rst="21" ❌ 正确应为 "12"✅ 正确做法:
// 方法1:逆序输出vector<char> digits;while (N) { digits.push_back(N%8+'0'); N/=8; }reverse(digits.begin(), digits.end());// 方法2:字符串头插string rst = "";while (N) { rst = char(N%8+'0') + rst; N/=8; }❌ 易错点分析:考生可能忽略进制转换中"逆序输出"的关键步骤,导致判断错误;或混淆 string(s) 的构造方式(s[0] 后无 \0 可能导致未定义行为)。
🔗 2. 链表
📖 知识点详解
链表是动态数据结构,节点在内存中分散存储,通过指针连接。与数组对比:
| 存储方式 | ||
| 随机访问 | ||
| 插入/删除 | ||
| 空间开销 | ||
| 大小扩展 |
🔄 链表类型:
• 单链表:node → next → ... → nullptr • 双链表:... ↔ prev ↔ node ↔ next ↔ ... • 循环链表:尾节点 next 指向头节点,遍历终止条件为 p != head
🎯 考查方式与易错点
📝 真题示例
1️⃣ 链表与数组特性辨析(2024年9月单选题第1题)
🔍 题目:下面关于链表和数组的描述,错误的是()。
A. 数组大小固定,链表大小可动态调整。B. 数组支持随机访问,链表只能顺序访问。C. 存储相同数目的整数,数组比链表所需的内存多。D. 数组插入和删除元素效率低,链表插入和删除元素效率高。✅ 【答案】C
🔎 【解析】数组仅存储数据本身,而链表每个节点除存储数据外还需存储指针(通常4/8字节)。因此存储相同数目数据时,链表所需内存通常更多。选项C描述错误。
💡 记忆口诀:链表"灵活"但"费空间",数组"紧凑"但"僵化"。
❌ 易错点分析:考生可能直觉认为链表更"轻便",从而误选C。实际上,链表的指针开销使其内存占用更大。
2️⃣ 双链表插入操作(2024年6月单选题第2题)
🔍 题目:通过()操作,能完成在双向循环链表结点 p 之后插入结点 s 的功能(其中 next 域为结点的直接后继,prev 域为结点的直接前驱)。
✅ 【答案】D
// ✅ 正确顺序(选项D):s->next = p->next; // ① s指向原后继p->next->prev = s; // ② 原后继指向ss->prev = p; // ③ s指向pp->next = s; // ④ p指向s(最后断开原链接)🔎 【解析】双向链表插入需修改4个指针,关键在于在断开原有链接前,先利用原有指针获取目标节点地址。选项D的顺序确保了:
1. 先保存 p->next的值(通过s->next = p->next)2. 再修改其他指针,避免链表断裂
🎯 通用技巧:插入操作"先连后断",删除操作"先记后连"。
❌ 易错点分析:考生易混淆指针修改的顺序。若先执行 p->next = s,则 p->next->prev 将访问错误节点,导致链表损坏。
3️⃣ 循环单链表遍历(2025年12月单选题第1题)
🔍 题目:对如下定义的循环单链表,横线处填写()。
structNode {int data; Node* next;Node(int d) : data(d), next(nullptr) {}};voidprintList(Node* head){if (head == nullptr) return; Node* p = head;// 在此处填入代码 cout << endl;}✅ 【答案】C
// ✅ 循环链表遍历(选项C):do { cout << p->data << " "; p = p->next;} while (p != head);🔎 【解析】循环链表的最后一个节点的 next 指向 head。遍历应使用 do-while 循环:
• 先输出当前节点(保证至少访问头节点) • 再移动指针 • 当 p == head时说明已遍历一圈,停止
⚠️ 对比普通单链表:
while (p != nullptr)会导致循环链表死循环!
❌ 易错点分析:考生可能直接套用单链表遍历模式(选项A或D),导致死循环或未输出所有节点。必须注意循环链表的特殊终止条件 p != head。
🔄 3. 递归与分治
📖 知识点详解
⚠️ 递归风险:
• 🚫 无终止条件 → 栈溢出(Stack Overflow) • 🚫 重复计算 → 指数级时间复杂度(如朴素斐波那契 O(2^n)) • ✅ 优化方案:记忆化搜索 / 动态规划 / 转迭代
🎯 考查方式与易错点
📝 真题示例
1️⃣ 递归效率与溢出(2023年9月单选题第4题)
🔍 题目:有关下面C++代码说法错误的是()。
intsumA(int n){ // 迭代版本int sum = 0;for (int i = 1; i < n + 1; i++) sum += i;return sum;}intsumB(int n){ // 递归版本if (n == 1) return1;elsereturn n + sumB(n - 1);}A. sumA() 用循环方式求从 1 到 N 之和,sumB() 用递归方式求从 1 到 N 之和。B. 默认情况下,如果输入正整数 1000,能实现求从 1 到 1000 之和。C. 默认情况下,如果输入正整数 100000,能实现求从 1 到 100000 之和。D. 一般来说,sumA() 的效率高于 sumB()。✅ 【答案】C
🔎 【解析】
• 数据溢出:1到100000的和 = 100000×100001/2 = 5,000,050,000,超过int最大值2,147,483,647,发生整数溢出,结果错误。• 效率对比:递归版本 sumB()有函数调用开销(压栈、跳转),效率低于迭代版本sumA()。
💡 拓展:若需计算大数和,应使用
long long或高精度运算。
❌ 易错点分析:考生可能只关注递归效率问题,而忽略了大数据求和结果溢出的问题。需要综合考虑数据范围和算法实现。
2️⃣ 分治思想辨析(2024年12月单选题第14题)
🔍 题目:关于分治算法,以下说法中不正确的是()。
A. 分治算法将问题分成子问题,然后分别解决子问题,最后合并结果。B. 归并排序采用了分治思想。C. 快速排序采用了分治思想。D. 冒泡排序采用了分治思想。✅ 【答案】D
🔎 【解析】
• ✅ 分治三要素:分解(Divide)、解决(Conquer)、合并(Combine) • ✅ 归并排序:分解数组→递归排序→合并有序子数组 • ✅ 快速排序:选基准→分区→递归排序左右子数组 • ❌ 冒泡排序:通过相邻元素交换逐步"冒泡",是迭代+贪心思想,无"分解-合并"过程
🎯 判断技巧:若算法过程中存在"将问题规模减半"的递归结构,大概率是分治。
❌ 易错点分析:考生可能对"分治"的核心思想理解不深,误认为所有排序算法都蕴含分治思想。
3️⃣ 递归函数输出序列(2023年12月单选题第3题)
🔍 题目:阅读下面的 C++ 代码,执行后其输出是()。
int stepCount = 0;intfracA(int N){ // 迭代求阶乘 stepCount += 1; cout << stepCount << "->";int rtn = 1;for (int i = 1; i <= N; i++) rtn *= i;return rtn;}intfracB(int N){ // 递归求阶乘 stepCount += 1; cout << stepCount << "->";if (N == 1) return1;return N * fracB(N - 1);}intmain(){ cout << fracA(5); cout << "<==="; cout << fracB(5);return0;}✅ 【答案】D1->120<===>2->3->4->5->6->120
🔎 【解析】
💡 追踪技巧:用"调用栈"模拟递归过程,注意全局变量在递归中的累积效应。
❌ 易错点分析:考生需准确追踪全局变量 stepCount 在递归过程中的变化,并理解递归调用栈的先深入后返回执行顺序。
🎯 4. 贪心算法
📖 知识点详解
⚠️ 核心警示:🚫 贪心 ≠ 万能!使用前必须证明策略正确性,否则可能得到次优解。✅ 验证方法:构造反例 / 数学归纳 / 交换论证
🎯 考查方式与易错点
📝 真题示例
1️⃣ 贪心性质判断(2023年12月判断题第4题)
🔍 题目:贪心算法可以达到局部最优,但可能不是全局最优解。()
✅ 【答案】正确
🔎 【解析】贪心算法通过每一步选择局部最优解来尝试获得全局最优解,但并不保证一定能找到全局最优解,这是贪心算法的核心特性。
💡 经典反例:0-1背包问题中,按"价值/重量"贪心可能不如动态规划最优。
❌ 易错点分析:考生可能误以为"贪心"总是高效的,从而混淆其"不一定最优"的性质。
2️⃣ 贪心策略应用(2024年12月单选题第13题)
🔍 题目:假设有多个孩子,数组 g 保存所有孩子的胃口值。有多块饼干,数组 s 保存所有饼干的尺寸。小杨给孩子们发饼干,每个孩子最多只能给一块饼干。饼干的尺寸大于等于孩子的胃口时,孩子才能得到满足。小杨的目标是尽可能满足越多数量的孩子,因此打算采用贪心算法来找出能满足的孩子的数目,则横线上应填写的代码为()。
intcooki4children(vector<int>& g, vector<int>& s){sort(g.begin(), g.end());sort(s.begin(), s.end());int index = s.size() - 1;int result = 0;for (int i = g.size() - 1; i >= 0; i--) {if (index >= 0 && s[index] >= g[i]) { result++; index--; } }return result;}✅ 【答案】Aresult++; index--;
🔎 【解析】贪心策略:优先用最大的饼干满足需求最大的孩子(避免大饼干浪费在小胃口上)。代码逻辑:
1. 双排序:胃口和饼干均从小到大 2. 双指针:从末尾(最大值)向前遍历 3. 匹配成功:计数+1,饼干指针前移
🎯 变式思考:若目标是最小化饼干浪费,策略是否相同?(答案:是,因"满足最多孩子"等价于"最小化未满足")
❌ 易错点分析:考生可能对贪心策略的具体实现(从大到小匹配)理解不清,导致索引移动方向错误;或混淆"满足孩子数"与"剩余饼干数"的更新逻辑。
🔍 5. 二分查找与二分答案
📖 知识点详解
⚠️ 关键区别:
• 🔹 二分查找:在已知有序序列中找目标值 • 🔹 二分答案:在答案的可能范围中找最优解,需配合 check(mid)函数
🎯 考查方式与易错点
📝 真题示例
1️⃣ 二分查找前提(2024年3月判断题第3题)
🔍 题目:二分查找要求被搜索的序列是有序的,否则无法保证正确性。
✅ 【答案】正确
🔎 【解析】二分查找的核心是每次根据中间元素排除一半的搜索区间,这必须依赖序列的有序性。若序列无序,中间元素的位置无法提供关于目标位置的有效信息,查找可能失败。
💡 拓展:若序列部分有序(如旋转数组),需修改二分逻辑,但核心仍是"利用有序性排除区间"。
❌ 易错点分析:考生可能忽视二分查找的基础前提,认为它是一种通用的搜索技巧。
2️⃣ 链表不适合二分查找(2024年6月判断题第4题)
🔍 题目:在 C++ 中,可以使用二分法查找链表中的元素。
✅ 【答案】错误
🔎 【解析】二分查找需要能够快速(常数时间)访问序列的中间元素。链表只能顺序访问,获取中间元素需要遍历半个链表,时间复杂度为 O(n),失去了二分查找 O(log n) 的高效性。
🎯 对比记忆:
• ✅ 数组: arr[mid]→ O(1)• ❌ 链表:需 mid次next指针跳转 → O(n)
❌ 易错点分析:考生可能混淆"链表可以排序"和"链表可以二分查找"这两个概念。
3️⃣ 二分查找左边界(2024年12月单选题第11题)
🔍 题目:给定一个长度为 n 的有序数组 nums,其中可能包含重复元素。下面的函数返回数组中某个元素 target 的左边界,若数组中不包含该元素,则返回 -1。则横线上应填写的代码为()。
intgetLeftBoundary(vector<int>& nums, int target){int left = 0;int right = nums.size() - 1;while (left < right) {int middle = left + ((right - left) / 2);if (target <= nums[middle]) right = middle;else left = middle + 1; }return nums[left] == target ? left : -1;}✅ 【答案】Bright = middle;
🔎 【解析】寻找左边界的核心逻辑:
• 当 target <= nums[middle]:目标可能在middle或其左侧,收缩右边界到middle(保留mid)• 当 target > nums[middle]:目标在右侧,收缩左边界到middle+1
💡 边界模板:
// 找左边界(第一个>=target的位置)while (left < right) { mid = left + (right-left)/2;if (check(mid)) right = mid;else left = mid + 1;}// 找右边界(最后一个<=target的位置)while (left < right) { mid = left + (right-left+1)/2;if (check(mid)) left = mid;else right = mid - 1;}❌ 易错点分析:考生可能套用普通二分查找的边界更新策略(right = middle - 1),导致未能正确找到左边界;或忽略 mid 计算时的整数溢出风险。
🔢 6. 高精度运算
📖 知识点详解
高精度运算用于处理超出标准数据类型范围的大整数(如 100!),通常用数组模拟竖式运算:
| 加法 | ||
| 减法 | ||
| 乘法 | ||
| 除法 |
💡 通用技巧:
• ✅ 数组低位存低位( a[0]是个位),方便进位处理• ✅ 运算后去除前导零(注意结果全0时保留一个0) • ✅ 减法前比较大小,确保被减数≥减数(或处理负号)
🎯 考查方式与易错点
📝 真题示例
1️⃣ 高精度加法实现(2023年12月单选题第11题)
🔍 题目:下面的 C++ 代码使用数组模拟整数加法,可以处理超出大整数范围的加法运算。横线处应填入代码是()。
vector<int> operator +(vector<int> a, vector<int> b) { vector<int> c;int t = 0;for (int i = 0; i < a.size() || i < b.size(); i++) {if (i < a.size()) t += a[i];if (i < b.size()) t += b[i]; c.push_back(t % 10); t = t / 10; }if (t) c.push_back(t);return c;}✅ 【答案】Dc.push_back(t % 10), t = t / 10;
🔎 【解析】t 表示当前位的和(含上一位进位):
• t % 10:取个位作为当前位结果• t / 10:取十位及以上作为新进位
💡 示例:
7+8=15→c.push_back(5),t=1(进位)
❌ 易错点分析:考生可能混淆取模和整除的作用,导致结果位或进位处理错误;或忘记处理最后可能的进位。
2️⃣ 高精度减法借位(2024年6月单选题第12题)
🔍 题目:要实现一个高精度减法函数,则下面代码中加划线应该填写的代码为()。
vector<int> minus(vector<int> a, vector<int> b){ vector<int> c;for (int i = 0; i < b.size(); i++) {if (a[i] < b[i]) { a[i + 1]--; } a[i] += 10; c.push_back(a[i] - b[i]); }return c;}✅ 【答案】Aa[i + 1]--;
🔎 【解析】高精度减法借位规则:
1. 当 a[i] < b[i]时,向高位(i+1)借12. 借位后: a[i] += 10,a[i+1] -= 13. 当前位结果: a[i] - b[i]
💡 记忆口诀:"不够减,向前借;借1当10,高位减1"
❌ 易错点分析:考生可能对高精度减法的借位方向(向高位)理解模糊,或混淆操作对象(应修改被减数 a 而非减数 b)。
⏱️ 7. 算法复杂度分析
📖 知识点详解
💡 复杂度速查表:
O(1) : 数组访问、哈希查找O(log n) : 二分查找、平衡树操作O(n) : 线性遍历、单循环O(n log n): 归并/快排、堆操作O(n²) : 冒泡/插入排序、双重循环O(2ⁿ) : 朴素递归斐波那契、子集枚举🎯 考查方式与易错点
📝 真题示例
1️⃣ 素数判定复杂度对比(2023年12月单选题第8题)
🔍 题目:下面C++代码中的 isPrimeA() 和 isPrimeB() 都用于判断参数 N 是否素数,有关其时间复杂度的正确说法是()。
boolisPrimeA(int N){for (int i = 2; i <= N / 2; i++) ...}boolisPrimeB(int N){for (int i = 2; i <= sqrt(N); i++) ...}✅ 【答案】BisPrimeA: O(N), isPrimeB: O(√N)
🔎 【解析】
• isPrimeA:最坏循环N/2次 → O(N)• isPrimeB:最坏循环√N次 → O(√N) = O(N^{1/2})
💡 优化本质:若
N = a×b且a≤b,则a ≤ √N,只需检查到√N即可。
❌ 易错点分析:考生可能对 sqrt(N) 对应的复杂度表示不熟悉,误用 O(log N) 描述;或混淆 N/2 与 N 在渐进意义下等价(均为 O(N))。
2️⃣ 递归斐波那契复杂度(2024年3月单选题第6题)
🔍 题目:下面的代码片段用于计算斐波那契数列。该代码的时间复杂度是()?
intfibonacci(int n){if (n <= 1) return n;returnfibonacci(n-1) + fibonacci(n-2);}✅ 【答案】CO(2^n)
🔎 【解析】朴素递归斐波那契的调用树:
fib(5)├─ fib(4)│ ├─ fib(3)│ │ ├─ fib(2) ...│ │ └─ fib(1)│ └─ fib(2)└─ fib(3) ...• 每层节点数约翻倍 → 总节点数 ≈ 2^n• 每个节点 O(1)操作 → 总时间O(2^n)
✅ 优化方案:记忆化搜索(
O(n)) / 迭代(O(n)) / 矩阵快速幂(O(log n))
❌ 易错点分析:考生可能误认为递归的深度是 n,从而得出 O(n) 的错误结论。实际上,递归树的节点总数才是时间复杂度的关键。
📊 二、编程题考查内容分类总结
| 🔢 数论与质因数分解 | |||
| 🎯 贪心算法 | |||
| 🔄 分治与递归 | |||
| 🔢 高精度运算 | |||
| 🔗 链表与数据结构 | |||
| 🧩 综合应用 |
🎯 命题趋势:近年题目更侧重多知识点融合(如"二分答案+贪心验证"),建议加强综合训练。
🧠 三、高频知识点记忆表
| 🔢 素数判定 | |||
| 🌱 线性筛法 | |||
| ✖️ 唯一分解定理 | |||
| 🔗 链表操作 | |||
| 🔄 递归复杂度 | |||
| 🎯 贪心算法 | |||
| 🔍 二分查找 | |||
| 🔢 高精度运算 |
💡 终极建议:1️⃣ 代码规范:变量命名清晰、关键步骤加注释2️⃣ 边界测试:空输入、极值、重复元素、0/1特判3️⃣ 复杂度意识:写代码前先估算,避免超时4️⃣ 错题复盘:记录易错点,定期回顾强化
🌟 祝各位学员备考顺利,金榜题名! 🎉📌 本资料保留原题引用及解析,扩充内容仅供学习参考,请以官方考纲为准