GESP认证C++编程真题解析 | 202506 五级
欢迎大家订阅我的专栏:算法题解:C++与Python实现!
本专栏旨在帮助大家从基础到进阶 ,逐步提升编程能力,助力信息学竞赛备战!
专栏特色
1.经典算法练习:根据信息学竞赛大纲,精心挑选经典算法题目,提供清晰的代码实现与详细指导,帮助您夯实算法基础。
2.系统化学习路径:按照算法类别和难度分级,从基础到进阶,循序渐进,帮助您全面提升编程能力与算法思维。
适合人群:
- 准备参加蓝桥杯、GESP、CSP-J、CSP-S等信息学竞赛的学生
- 希望系统学习C++/Python编程的初学者
- 想要提升算法与编程能力的编程爱好者
附上汇总帖:GESP认证C++编程真题解析 | 汇总
考情分析
一、单项选择题(共15题)
| 题号 | 考察知识点 |
|---|---|
| 1 | 数据结构基础 — 链表 vs 数组的特性对比:链表在已知位置插入/删除为 \(O(1)\),数组为 \(O(n)\) |
| 2 | C++语法与指针 — 双向链表判空条件:head == nullptr、tail == nullptr、size == 0 均可,但 head.data 对空指针解引用非法 |
| 3 | 数据结构操作 — 双向链表尾部插入:需维护 tail->next、newNode->prev、tail 三个指针关系 |
| 4 | 数据结构操作 — 循环链表节点删除:先改指针再 delete,避免悬空指针;prev->next = p->next 必须在 delete p 之前 |
| 5 | 数论算法 — 质数判定的 \(6k \pm 1\) 优化:所有大于3的质数必为 \(6k \pm 1\) 形式,减少试除次数 |
| 6 | 算法复杂度分析 — GCD算法对比:gcd0 辗转相除 \(O(\log \min(a,b))\) vs gcd1 枚举 \(O(\min(a,b))\) |
| 7 | 算法效率对比 — 素数筛法:线性筛 \(O(n)\) > 埃氏筛 \(O(n \log \log n)\) > 单个试除法(批量场景) |
| 8 | 数论定理 — 唯一分解定理(算术基本定理):大于1的合数可唯一分解为质数乘积 |
| 9 | 算法思想辨析 — 分治 vs 贪心 vs 递推:找最大值的分治递归实现 |
| 10 | 算法复杂度分析 — 迭代找最大值 \(O(n)\),与递归分治时间复杂度相同,但无栈开销 |
| 11 | 二分查找变体 — 查找最后一个出现位置:mid = (low + high + 1) / 2 向上取整防死循环,偏向右侧 |
| 12 | 数值算法 — 二分法求平方根:阶段1找整数部分,阶段2精确定位;浮点数比较 high_d - low_d >= epsilon 是标准做法 |
| 13 | 贪心算法 — 硬币找零:贪心策略(从大到小取)不一定最优,反例 \(coins=[5,4,1], amount=8\) |
| 14 | 排序算法 — 快速排序:随机化选基准防最坏情况;不稳定排序;平均 \(O(n \log n)\) |
| 15 | 高精度算法 — 大整数除法:模拟手工除法的"落位"操作,高位移入余数最低位 |
二、判断题(共10题)
| 题号 | 考察知识点 |
|---|---|
| 1 | 数论算法 — 欧几里得算法正确性:无论 \(a > b\) 或 \(a < b\),\(a \% b\) 保证规模递减 |
| 2 | 数论公式 — \(\text{lcm}(a,b) = \frac{a \times b}{\gcd(a,b)}\) |
| 3 | C++语法 — vector 操作:prime_factor[i] 是 vector<int>,不能与整数直接相加,需用 push_back |
| 4 | 递归分析 — 归并排序递归树:merge 被调用 \(n-1\) 次(每次合并两个子数组),HERE 输出多次 |
| 5 | 排序算法 — 归并排序时间复杂度:最好/最坏/平均均为 \(O(n \log n)\),与输入有序性无关 |
| 6 | 算法思想 — 二分查找应用:字典查找满足有序性和范围减半,是二分查找的典型场景 |
| 7 | 图论算法 — Dijkstra算法:每一步选当前距离最小的节点,属于贪心策略 |
| 8 | 算法思想 — 分治效率:分治通过降低问题规模,通常效率高于直接求解(如归并排序 \(O(n \log n)\) vs 直接 \(O(n^2)\)) |
| 9 | 递归终止 — \(3n+1\) 问题(Collatz猜想):对 \(n=7\) 经有限步必到达1,递归终止 |
| 10 | 素数筛法 — 线性筛正确性:每个合数仅被最小质因数筛一次,i \% primes[j] == 0 时 break 保证 |
三、编程题(共2题)
题目一:奖品兑换(P13013)
预期考察: 动态规划 / 贪心 / 完全背包
典型思路:
- 给定若干种奖品及其所需积分,求兑换特定奖品的最小积分或最大价值
- 可能涉及完全背包模型:每种奖品可选无限次,求最大价值或最小代价
题目二:最大公因数(P13014)
预期考察: 数论 / GCD / 辗转相除
典型思路:
- 求多个数的最大公因数
- 利用性质:\(\gcd(a_1, a_2, \ldots, a_n) = \gcd(\gcd(a_1, a_2), a_3, \ldots, a_n)\)
单选题
第1题
与数组相比,链表在( )操作上通常具有更高的效率。
A.随机访问元素
B.查找指定元素
C.在已知位置插入或删除节点
D.遍历所有元素
【答案】:C
【解析】
链表的插入和删除操作通常只需要修改指针,不需要移动元素,因此效率较高。
第2题
下面C++代码实现双向链表。函数 is_empty() 判断链表是否为空,如链表为空返回 true ,否则返回 false 。横线处不能填写( )。
// 节点结构体
struct Node {
int data;
Node* prev;
Node* next;
};
// 双向链表结构体
struct DoubleLink {
Node* head;
Node* tail;
int size;
DoubeLink() {
head = nullptr;
tail = nullptr;
size = 0;
}
~DoubleLink() {
Node* curr = head;
while (curr) {
Node* next = curr->next;
delete curr;
curr = next;
}
}
// 判断链表是否为空
bool is_empty() const {
______
}
};
A.return head == nullptr;
B.return tail == nullptr;
C.return head.data == 0;
D.return size == 0;
【答案】:C
【解析】
A选项表示头节点为空指针,B选项表示尾节点为空指针,C选项表示头节点数据域为0,D选项表示链表大小为0。
因此A、B、D选项都可填写,只有C选项不可填写。
第3题
基于上题代码正确的前提下,填入相应代码完善 append() ,用于在双向链表尾部增加新节点,横线上应填 写( )。
void append(int data) {
Node* newNode = new Node(data, nullptr, nullptr);
if (is_empty()) {
head = tail = newNode;
} else {
______
}
++size;
}
A.
tail->next = newNode;
B.
newNode->prev = tail;
tail = newNode;
C.
tail = newNode;
newNode->prev = tail;
tail->next = newNode;
D.
tail->next = newNode;
newNode->prev = tail;
tail = newNode;
【答案】:D
【解析】
D选项表示新节点的前指针指向尾节点,尾节点后指针指向新节点,新节点成为新的尾节点。
第4题
下列C++代码用循环链表解决约瑟夫问题,即假设 n 个人围成一圈,从第一个人开始数,每次数到第 k 个 的人就出圈,输出最后留下的那个人的编号。横线上应填写( )。
struct Node {
int data;
Node* next;
};
Node* createCircularList(int n) {
Node* head = new Node(1, nullptr);
Node* prev = head;
for (int i=2; i<=n; ++i) {
Node* node = new Node(i, nullptr);
prev->next = node;
prev = node;
}
prev->next = head;
return head;
}
int fingLastSurvival(int n, int k) {
Node* head = createCircularList(n);
Node* p = head;
Node* prev = nullptr;
while (p->next != p) {
for (int count = 1; count < k; ++count) {
prev = p;
p = p->next;
}
______
}
cout << "最后留下的人编号是:" << p->data << endl;
delete p;
return 0;
}
A.
prev->next = p->next;
delete p;
p = prev->next;
B.
delete p;
prev->next = p->next;
p = prev->next;
C.
delete p;
p = prev->next;
prev->next = p->next;
D.
prev->next = p->next;
p = prev->next;
delete p;
【答案】:A
【解析】
A选项表示删除当前节点,当前节点指向下一个节点。
第5题
下列C++代码判断一个正整数是否是质数,说法正确的是( )。
bool is_prime(int n) {
if (n <= 1)
return false;
if (n == 2 || n == 3 || n == 5)
return true;
if (n % 2 == 0 || n % 3 == 0 || n % 5 == 0)
return false;
int i = 7;
int step = 4;
int finish_number = sqrt(n) + 1;
while (i <= finish_number) {
if (n % i == 0)
return false;
i += step;
step = 6 - step;
}
return true;
}
A.代码存在错误,比如5是质数,但因为 5 % 5 余数是0返回了 false
B.finish_number 的值应该是 n / 2 ,当前写法将导致错误
C.当前 while 循环正确的前提是:所有大于3的质数都符合 6k±1 形式
D.while 循环修改如下,其执行效果和执行时间相同。
for (int i=2; i<finish_number; i++) {
if (n % i == 0)
return false;
}
return true;
【答案】:C
【解析】
本题中的程序应用了质数的6k±1规则:除了2和3之外的所有质数都可以表示为6k±1的形式,因为:
若数n能被2或3整除,则n为合数;
若数n不能被2或3整除,则其除以6的余数不能是0、2、3、4,只能是1或5(例如5余1、7余1、11余5等),因此必然属于6k±1的形式,
A选项错误,5会返回true;
B选项错误,枚举因数的上限为\(\sqrt n\),而不是n/2,因为若n可以被i整除,那么n/i也一定可以被i整除,而n/i一定小于n,因此i一定小于等于\(\sqrt n\);
D选项错误,原程序只枚举了6k±1形式的数,D选项程序枚举了\(2-\sqrt n\)的所有数,原程序时间都会更快一些。
第6题
下列C++代码用两种方式求解两个正整数的最大公约数,说法错误的是( )。
int gcd0(int big, int small) {
if (big < small) {
swap(big, small);
}
if (big % small == 0) {
return small;
}
return gcd0(small, big % small);
}
int gcd1(int big, int small) {
if (big < small) {
swap(big, small);
}
for (int i=small; i>=1; --i) {
if (big % i == 0 && small % i == 0)
return i;
}
return 1;
}
A.gcd0() 函数的时间复杂度为
B.gcd1() 函数的时间复杂度为
C.一般说来, gcd0() 的效率高于 gcd1()
D.gcd1() 中的代码 for (int i = small; i >= 1; --i) 应该修改为 for (int i = small; i > 1; --i)
【答案】:D
【解析】
A、B、C选项说法正确,D选项原程序无需修改,i=1时返回结果仍然正确。
第7题
下面的代码用于判断整数 是否是质数,错误的说法是( )。
bool is_prime(int n) {
if (n <= 1) return false;
int finish_number = static_cast<int>(sqrt(n)) + 1;
for (int i=2; i<finish_number; ++i) {
if (n % i == 0)
return false;
}
return true;
}
A.埃氏筛算法相对于上面的代码效率更高
B.线性筛算法相对于上面的代码效率更高
C.上面的代码有很多重复计算,因为不是判断单个数是否为质数,故而导致筛选出连续数中质数的效率不高
D.相对而言,埃氏筛算法比上面代码以及线性筛算法效率都高
【答案】:D
【解析】
D选项线性筛的算法效率高于埃氏筛,明显错误。A、B选项存在歧义,在批量判断多个数时说法正确,但对于仅判断单个数时,试除法效率高。
第8题 唯一分解定理描述了关于正整数的什么性质?
A.任何正整数都可以表示为两个素数的和。
B.任何大于1的合数都可以唯一分解为有限个质数的乘积。
C.两个正整数的最大公约数总是等于它们的最小公倍数除以它们的乘积。
D.所有素数都是奇数。
【答案】:B
【解析】
唯一分解定理是数论中的一个重要定理,它描述了任意一个大于1的整数都可以唯一地表示为质数的乘积。
第9题
下面的C++代码,用于求一系列数据中的最大值。有关其算法说法错误的是( )。
int find_max_recursive(const vector<int>& nums, int left, int right) {
if (left == right)
return nums[left];
int mid = left + (right - left) / 2;
int left_max = find_max_recursive(nums, left, mid);
int right_max = find_max_recursive(nums, mid+1, right);
return max(left_max, right_max);
}
int find_max(const vector<int>& nums) {
if (nums.empty()) {
throw invalid_argument("输入数组不能为空");
}
return find_max_recursive(nums, 0, nums.size()-1);
}
A.该算法采用分治算法
B.该算法是递归实现
C.该算法采用贪心算法
D.该算法不是递推算法
【答案】:C
【解析】
分治、贪心、递推都是常用的算法设计思想,它们的区别在于解决问题的策略不同。分治算法将问题分成多个子问题,分别解决子问题,最后合并子问题的解。贪心算法总是选择当前最优的解,不一定能得到全局最优解。递推算法通过已知的解推导出未知的解。本程序中使用的是分治算法,实现找输入数组中的最大值。
第10题
下面的C++代码,用于求一系列数据中的最大值。有关其算法说法错误的是( )。
int find_max(const vector<int>& nums) {
if (nums.empty()) {
throw invalid_argument("输入数组不能为空");
}
int max_value = nums[0];
for (int num : nums) {
if (num > max_value) {
max_value = num;
}
}
return max_value;
}
A.本题 find_max() 函数采用的是迭代算法
B.本题 find_max() 函数的时间复杂度为
C.和上一题的 find_max() 相比,因为没有递归,所以没有栈的创建和销毁开销
D.本题 find_max() 函数和上一题的 find_max() 时间复杂度相同
【答案】:D
【解析】
本题中的程序采用的是迭代算法,没有递归,时间复杂度为O(n)。
第11题
下面的 C++ 代码用于在升序数组 lst 中查找目标值 target 最后一次出现的位置。相关说法,正确的是( )。
int binary_search_last_occurrence(const vector<ing>& lst, int target) {
if (lst.empty()) return -1;
int low = 0, high = lst.size() - 1;
while (low < high) {
int mid = (low + high + 1) / 2;
if (lst[mid] <= target) {
low = mid;
} else {
high = mid - 1;
}
}
if (lst[low] == target)
return low;
else
return -1;
}
A.当 lst 中存在重复的 target 时,该函数总能返回最后一个 target 的位置,即便 lst 全由相同元素组成
B.当 target 小于 lst 中所有元素时,该函数会返回 0
C.循环条件改为 while (low <= high) 程序执行效果相同,且能提高准确性
D.将代码中 (low + high + 1) / 2 修改为 (low + high) / 2 效果相同
【答案】:A
【解析】
A选项正确,当存在重复值时,mid会不断向右移动,直到找到最后一个target;
B选项错误,当target大于Ist中所有元素时,i 该函数会返回-1;
C选项错误,循环条件更改后,当low == high时会导致死循环
D选项错误,是为了防止死循环和确保偏向右侧,去掉+1会导致mid偏向左侧,无法正确找到最后一个出现位置。
第12题
有关下面C++代码的说法,错误的是( )。
double sqrt_binary(long long n, double epsilon = 1e-10) {
if (n < 0) {
throw invalid_argument("输入必须为非负整数");
}
if (n==0 || n ==1) return n;
// 阶段1
long long low = 1, high = n;
long long k = 0;
while (low <= high) {
long long mid = (low + high) / 2;
long long mid_sq = mid * mid;
if (mid_sq == n) {
return mid;
} else if (mid_sq < n) {
k = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
long long next_k = k + 1;
if (next_k * next_k == n) {
return next_k;
}
// 阶段2
double low_d = (double)k;
double high_d = (double)(k+1);
double mid;
while (high_d - low_d >= epsilon) {
mid = (low_d + high_d) / 2;
double mid_sq = mid * mid;
if (mid_sq < n) {
low_d = mid;
} else {
high_d = mid;
}
}
double result = (low_d + high_d) / 2;
long long check_int = (long long)(result + 0.5);
if (check_int * check_int == n) {
return check_int;
}
return result;
}
A.“阶段1”的目标是寻找正整数 n 可能的正完全平方根
B.“阶段2”的目标是如果正整数 n 没有正完全平方根,则在可能产生完全平方根附近寻找带小数点的平方根
C.代码 check_int = (long long)(result + 0.5) 是检查因浮点误差是否为正完全平方根
D.阶段2的二分法中 high_d - low_d >= epsilon 不能用于浮点数比较,会进入死循环
【答案】:D
【解析】
A、B、C选项说法正确,D选项-high_d - low_d >= epsilon 是浮点数比较的常规做法,不会导致死循环,当差值小于epsilon时会终止。
第13题
硬币找零问题中要求找给客户最少的硬币。 coins 存储可用硬币规格,单位为角,假设规格都小于10 角,且一定有1角规格。 amount 为要找零的金额,约定必须为1角的整数倍。输出为每种规格及其数量,按规格从大 到小输出,如果某种规格不必要,则输出为0。下面是其实现代码,相关说法正确的是( )。
const int MAX_COINS = 10;
int result[MAX_COINS] = {0}; // 假设最多10种面额
int find_coins(const vector<int>& coins, int amount) {
sort(coins.begin(), coins.end(), greater<int>());
int n = coins.size();
for (int i = 0; i < n; ++i) {
int coin = coins[i];
int num = amount / coin;
result[i] = num;
amount -= num * coin;
if (amount == 0) break;
}
cout << "找零方案如下:" << endl;
for (int i = 0; i < n; ++i) {
cout << sorted_coins[i] << "角需要" << result[i] << "枚" << endl;
}
return 0;
}
A.上述代码采用贪心算法实现
B.针对本题具体要求,上述代码总能找到最优解
C.上述代码采用枚举算法
D.上述代码采用分治算法
【答案】:A
【解析】
本题中,coins=[5,4,1], amount=8,贪心算法会给出5+1+1+1(4枚),但最优解是4+4(2枚),所以B选项错误。
第14题
关于下述C++代码的快速排序算法,说法错误的是( )。
int randomPartition(std::vector<int>& arr, int low, int high) {
int random = low + rand() % (high - low + 1);
std::swap(arr[random], arr[high]);
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return i + 1;
}
void quickSort(std::vector<int>& arr, int low, int high) {
if (low < high) {
int pi = randomPartition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
A.在 randomPartition 函数中,变量 i 的作用是记录大于基准值的元素的边界
B.randomPartition 函数随机选择基准值,可以避免输入数据特定模式导致的最坏情况下时间复杂度\(O(n^2)\)
C.快速排序平均时间复杂度是\(O(nlogn)\)
D.快速排序是稳定排序算法
【答案】:D
【解析】
快速排序是不稳定排序。其他选项说法正确。
第15题
小杨编写了一个如下的高精度除法函数,则横线上应填写的代码为( )。
const int MAXN = 1005; // 最大位数
struct BigInt {
int d[MAXN]; // 存储数字,d[0]是个位,d[1]是十位,...
int len; // 数字长度
BigInt() {
memset(d, 0, sizeof(d));
len = 0;
}
};
// 比较两个高精度数的大小
int compare(BigInt a, BigInt b) {
if(a.len != b.len) return a.len > b.len ? 1 : -1;
for(int i = a.len - 1; i >= 0; i--) {
if(a.d[i] != b.d[i]) return a.d[i] > b.d[i] ? 1 : -1;
}
return 0;
}
// 高精度减法
BigInt sub(BigInt a, BigInt b) {
BigInt c;
for(int i = 0; i < a.len; i++) {
c.d[i] += a.d[i] - b.d[i];
if(c.d[i] < 0) {
c.d[i] += 10;
c.d[i+1]--;
}
}
c.len = a.len;
while(c.len > 1 && c.d[c.len-1] == 0) c.len--;
return c;
}
// 高精度除法(a/b,返回商和余数)
pair<BigInt, BigInt> div(BigInt a, BigInt b) {
BigInt q, r; // q是商,r是余数
if(compare(a, b) < 0) { // 如果a<b,商为0,余数为a
q.len = 1;
q.d[0] = 0;
r = a;
return make_pair(q, r);
}
// 初始化余数r为a的前b.len位
r.len = b.len;
for(int i = a.len - 1; i >= a.len - b.len; i--) {
r.d[i - (a.len - b.len)] = a.d[i];
}
// 逐位计算商
for(int i = a.len - b.len; i >= 0; i--) {
// 把下一位加入余数
if(r.len > 1 || r.d[0] != 0) {
for(int j = r.len; j > 0; j--) {
r.d[j] = r.d[j-1];
}
_______________________
} else {
r.d[0] = a.d[i];
r.len = 1;
}
// 计算当前位的商
while(compare(r, b) >= 0) {
r = sub(r, b);
q.d[i]++;
}
}
// 确定商的长度
q.len = a.len - b.len + 1;
while(q.len > 1 && q.d[q.len-1] == 0) q.len--;
// 处理余数前导零
while(r.len > 1 && r.d[r.len-1] == 0) r.len--;
return make_pair(q, r);
}
A.
r.d[0] = a.d[i];
r.len++;
B.
r.d[i] = a.d[i];
r.len++;
C.
r.d[i] = a.d[i];
r.len = 1;
D.
r.d[0] = a.d[i];
r.len = 1;
【答案】:A
【解析】
A选项是模拟手工除法时"落位"操作的标准实现,需要同时完成两个操作:
- 将新数字放入最低位
- 增加余数的位数
判断题
第1题
下面C++代码是用欧几里得算法(辗转相除法)求两个正整数的最大公约数, a 大于 b 还是小于 b 都适用。
int gcd(int a, int b) {
while (b) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
A.正确
B.错误
【答案】:A
【解析】
不论a与b的大小关系,a%b的结果一定小于b,算法继续执行,直到b为0,a就是最大公约数。
第2题
假设函数 gcd() 函数能正确求两个正整数的最大公约数,则下面的 lcm() 函数能求相应两数的最小公倍数。
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
A.正确
B.错误
【答案】:A
【解析】
最小公倍数等于两数之积除以最大公约数。
第3题
下面的C++代码用于输出每个数对应的质因数列表,输出形如: {5: [5], 6: [2, 3], 7: [7], 8: [2, 2, 2]} 。
int main() {
int n, m;
cin >> n >> m;
if (n > m) swap(n, m);
map<int, vector<int>> prime_factor;
for (int i = n; i <= m; ++i) {
int j = 2, k = i;
while (k != 1) {
if (k % j == 0) {
prime_factor[i] = prime_factor[i] + j;
k /= j;
} else {
++j;
}
}
}
for (auto& p : prime_factor) {
cout << p.first << ": ";
for (int v : p.second)
cout << v << " ";
cout << endl;
}
return 0;
}
A.正确
B.错误
【答案】:B
【解析】
第12行,prime_factor[i]不能直接与整数相加,应修改为 prime_factor[i].push_back(j);,输出格式也需要调整。
第4题
下面的C++代码实现归并排序。代码在执行时,将输出一次 HERE 字符串,因为merge()函数仅被调用一次。
void merge(std::vector<int>& arr, int left, int mid, int right) {
std::vector<int> temp(right - left + 1);
int i = left;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int p = 0; p < k; ++p) {
arr[left + p] = temp[p];
}
}
void mergeSort(std::vector<int>& arr, int left, int right) {
if (left >= right) {
return;
}
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
std::cout << "HERE";
merge(arr, left, mid, right);
}
A.正确
B.错误
【答案】:B
【解析】
每次递归调用mergeSort()时,只要数组长度大于1,就会输出HERE字符串,并调用 merge() 函数。因此 HERE 会被输出多次。
第5题
归并排序的最好、最坏和平均时间复杂度均为 \(O(nlogn)\)。
A.正确
B.错误
【答案】:A
【解析】
归并排序的时间复杂度与原始数组的有序程度无关,无论数组是否有序,时间复杂度均为O(nlogn)。
第6题
查字典这个小学生必备技能,可以把字典视为一个已排序的数组。假设小杨要查找一个音首字母为 g 的单 词,他首先翻到字典约一半的页数,发现该页的首字母是 m ,由于字母表中 g 位于 m 之前,所以排除字典后半部 分,查找范围缩小到前半部分;不断重复上述步骤,直至找到首字母为 g 的页码。这种查字典的一系列操作可看作二分查找。
A.正确
B.错误
【答案】:A
【解析】
二分查找的前提是数据必须是有序的,字典按照字母顺序排列正好满足这一条件。每次查找都将查找范围缩小一半。题目描述的操作步骤与二分查找算法完全一致。
第7题
求解下图中A点到D点最短路径,其中A到B之间的12可以理解为距离。求解这样的问题常用Dijkstra算法,其 思路是通过逐步选择当前距离起点最近的节点来求解非负权重图(如距离不能为负值)单源最短路径的算法。从该 算法的描述可以看出,Dijkstra算法是贪心算法。

A.正确
B.错误
【答案】:A
【解析】
Dijkstra算法通过“每一步都选择当前距起点最短的节点”的策略逐步扩展最短路径,完全符合贪心算法的定义。
第8题
分治算法将原问题可以分解成规模更小的子问题,使得求解问题的难度降低。但由于分治算法需要将问题进 行分解,并且需要将多个子问题的解合并为原问题的解,所以分治算法的效率通常比直接求解原问题的效率低。
A.正确
B.错误
【答案】:B
【解析】
分治算法的时间复杂度与原问题的分解方式有关。
第9题
函数 puzzle 定义如下,则调用 puzzle(7) 程序会无限递归。
int puzzle(int n) {
if (n == 1) return 1;
if (n % 2 == 0) return puzzle(n / 2);
return puzzle(3 * n + 1);
}
A.正确
B.错误
【答案】:B
【解析】
该函数实现的是3n+1问题,r n依次变为7->22->11->34->17->52->26->13->40->20->10->5->16->8->4->2->1。可以看到,当最终n为1时,递归结束。
第10题
如下为线性筛法,用于高效生成素数表,其核心思想是每个合数只被它的最小质因数筛掉一次,时间复杂度为 \(O(n)\)。
vector<int> linearSieve(int n) {
vector<bool> is_prime(n + 1, true);
vector<int> primes;
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.正确
B.错误
【答案】:A
【解析】
线性筛法(也称欧拉筛法)是是一种高效生成质数的算法,核心目标是“用最小质因数筛掉每个合数,且每个合数只被筛一次”。其中第13行的break保证了每个合数都只被其最小质因数筛一次。
浙公网安备 33010602011771号