CCF编程能力等级认证GESP—C++5级—20250322

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

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

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

正确答案:A
考察知识点:链表的特性
解析:
选项A:链表需从头节点顺序访问(O(n)),不支持随机访问(数组通过索引O(1)访问),A正确。
其他选项:链表插入删除无需移动元素(B错误);动态分配空间(C错误);存储空间与元素个数成正比(含指针开销,D错误)。

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
考察知识点:双向链表的删除操作
解析:
正确步骤:删除节点p需将p的前驱节点的next指向p->next,p的后继节点的prev指向p->prev。
选项A错误:p->next->prev = p->next会导致p的后继节点的前驱指向自身,破坏链表结构。

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
考察知识点:双向循环链表的初始化
解析:
哨兵节点作用:头哨兵head和尾哨兵tail简化边界判断,空链表时head->next = tail且tail->prev = head,形成循环。
选项B正确:初始化后链表为空,头哨兵指向尾哨兵,尾哨兵指回头哨兵。

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. 8460
B. 6024
C. 2412
D. 120

正确答案:B
考察知识点:辗转相除法的步骤
解析:
计算流程:
gcd(84,60) → 84%60=24 → 调用gcd(60,24)(第二步);
gcd(60,24) → 60%24=12 → 调用gcd(24,12);
返回12。

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

A. 18 = 3 × 6
B. 28 = 4 × 7
C. 36 = 2 × 3 × 6
D. 30 = 2 × 3 × 5

正确答案:D
考察知识点:唯一分解定理
解析:
唯一分解定理要求分解为素数乘积,选项D中30=2×3×5(均为素数)正确。其他选项含合数因子(如6=2×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/2; i++) {
		if (is_prime[i])
			primes.push_back(i);
	for (int j = 0; ____; 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
考察知识点:线性筛法的循环条件
解析:
内层循环条件:需遍历已找到的素数(j < primes.size()),且i*primes[j] <=n(避免越界),选项C正确。

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

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

正确答案:A
考察知识点:递归的栈溢出
解析:
递归调用时系统为每层函数分配栈空间,层数过多会导致栈溢出,A正确。

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)。
C. factorialA采用递归方式。
D. factorialB采用递归方式。

正确答案:D
考察知识点:递归与迭代的实现
解析:
factorialA是递归,factorialB是迭代(for循环),D错误。

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

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

正确答案:A
考察知识点:排序算法的稳定性
解析:
选择排序不稳定:例如[2,2,1],第一次选择1会与第一个2交换,破坏原有顺序。其他选项均为稳定排序。

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
考察知识点:快速排序的分区操作
解析:
分区逻辑:将小于基准值pivot的元素交换到左侧,i记录小于区边界。选项B正确:if (arr[j] < pivot) {i++; swap(arr[i], arr[j]);}。

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

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

正确答案:C
考察知识点:二分查找的最多次数
解析:
二分查找次数满足2^k ≥ 100,k=7(2⁷=128≥100),C正确。

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

int binarySearch(int arr[], int left, int right, int target) {
	while (left <= right) {
		________// 在此处填入代码
		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
考察知识点:二分查找的中点计算
解析:
防溢出写法:mid = left + (right - left)/2等价于(left+right)/2,但避免left+right溢出,A正确。

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

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

正确答案:A
考察知识点:贪心算法的核心
解析:
贪心算法每次选择局部最优解,A正确。B是回溯,C是分治,D错误(贪心不一定全局最优)。

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正确实现此逻辑。

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
考察知识点:高精度乘法的进位处理
解析:
进位计算:当前位值c[k]加上上一轮进位carry,再取模和商,选项B正确。

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

1、单链表中删除某个结点 p (非尾结点),但不知道头结点,可行的操作是将 p 的值设为 p->next 的值,然后
删除 p->next 。

正确答案:正确
解析:单链表删除非尾节点p,可复制p->next的值到p,再删除p->next,正确。

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

正确答案:错误
解析:链表节点内存分散,通过指针连接,无需连续空间,错误。

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

正确答案:正确
解析:线性筛每个合数仅被最小质因子标记,效率高于埃氏筛,正确。

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

正确答案:错误
解析:贪心算法可能得到局部最优而非全局最优(如找零钱问题),错误。

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

正确答案:正确
解析:递归需终止条件,否则无限递归导致栈溢出,正确。

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

正确答案:错误
解析:快速排序在有序数组上时间复杂度为O(n²),错误。

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

正确答案:正确
解析:归并排序时间复杂度始终为O(n log n),与输入无关,正确。

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

正确答案:错误
解析:二分查找仅适用于有序数组,错误。

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

正确答案:错误
解析:策略为每次选价格最低商品,体现贪心思想,非分治,错误。

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

正确答案:正确
解析:归并排序分治流程:二分→子问题排序→合并,正确。

编程题 (每题 25 分,共 50 分)

平均分配

【问题描述】
小 A 有2n件物品,小 B 和小 C 想从小 A 手上买走这些物品。对于第i件物品,小 B 会以 b i b_i bi的价格购买,而小 C 会以 c i c_i ci的价格购买。为了平均分配这2n件物品,小 A 决定小 B 和小 C 各自只能买走恰好n件物品。你能帮小 A 求出他卖出这2n件物品所能获得的最大收入吗?
【输入格式】
第一行,一个正整数n。
第二行,2n个整数 b 1 , b 2 , . . . , b 2 n b_1, b_2, ..., b_{2n} b1,b2,...,b2n
第三行,2n个整数 c 1 , c 2 , . . . , c 2 n c_1, c_2, ..., c_{2n} c1,c2,...,c2n
【输出格式】
一行,一个整数,表示答案。
【样例输入 1】
3
1 3 5 6 8 10
2 4 6 7 9 11
【样例输出 1】
36
【样例输入 2】
2
6 7 9 9
1 2 10 12
【样例输出 2】
35
【数据范围】
对于20%的测试点,保证 1 ≤ n ≤ 8 1 \le n \le 8 1n8
对于另外20%的测试点,保证 0 ≤ b i ≤ 1 , 0 ≤ c i ≤ 1 0 \le b_i \le 1,0 \le c_i \le 1 0bi10ci1
对于所有测试点,保证 1 ≤ n ≤ 1 0 5 , o ≤ b i ≤ 1 0 9 , 0 ≤ c i ≤ 1 0 9 1 \le n \le 10^5, o \le b_i \le 10^9, 0 \le c_i \le 10^9 1n105obi1090ci109

原根判断

【问题描述】
小 A 知道,对于质数p而言,p的原根g是满足以下条件的正整数:

  • 1 < g < p 1 \lt g \lt p 1<g<p
  • g p − 1 g^{p-1} gp1 mod p = 1;
  • 对于任意 q ≤ i < p − 1 q \le i \lt p-1 qi<p1均有 g i g^i gi mod p ≠ \neq = 1。

其中a mod p 表示 a 除以p的余数。
小A现在有一个整数a,请你帮他判断a是不是p的原根
【输入格式】
第一行,一个正整数T,表示测试数据组数。
每组测试数据包含一行,两个正整数a, p。
【输出格式】
对于每组测试数据,输出一行,如果a是p的原根则输出 Yes ,否则输出 No 。
【样例输入 1】
3
3 998244353
5 998244353
7 998244353
【样例输出 1】
Yes
Yes
No
【数据范围】
对于40%的测试点,保证 3 ≤ p ≤ 1 0 3 3 \le p \le 10^3 3p103
对于所有测试点,保证 1 ≤ T ≤ 20 , 3 ≤ p ≤ 1 0 9 , 1 < a < p 1 \le T \le 20,3 \le p \le 10^9,1 \lt a \lt p 1T203p1091<a<p,p为质数。

### 关于CCF GESP C++ 5编程能力等级认证模拟题解析 #### 模拟题目一:多态机制的理解与应用 当定义基类指针指向派生类对象并调用虚函数时,实际会调用派生类中的版本。因此,在给定的代码片段中创建了一个`base`类型的指针`b`以及一个`derived`实例`d`,接着让`b`指向`d`,最后通过`b->show()`来触发成员方法调用。由于存在继承关系且`show`被声明为虚拟函数,所以最终输出将是“derived class”[^1]。 ```cpp #include <iostream> using namespace std; class base { public: virtual void show() { cout << "base class" << endl; } }; class derived : public base { public: void show() override { cout << "derived class" << endl; } }; int main() { base* b; derived d; b = &d; b->show(); return 0; } ``` #### 模拟题目二:数组越界访问风险分析 对于第二个例子而言,程序试图打印字符数组`geSP`中位于索引位置等于整型数组`x`大小处的那个元素。考虑到`sizeof(x)`返回的是整个数组占用字节数而非元素数量,并且假设每个整形占四个字节,则该表达式的计算结果应为16(即4 * sizeof(int)),这显然超出了字符串的实际长度范围。这种情况下可能会导致未定义行为的发生,具体表现为可能显示随机内存内容或引发异常终止等问题[^2]。 ```cpp int main() { int x[] = {2, 0, 2, 4}; char geSP[] = "Grade Examination of SP"; cout << geSP[sizeof(x)] << endl; cout << endl; return 0; } ``` #### 模拟题目三:不常见调试技巧的选择 面对复杂逻辑错误或者难以定位的问题时,通常建议采用多种方式相结合来进行排查工作。选项A至C均属于较为常规有效的做法;然而D项涉及到了汇编层面的操作,除非开发者具备深厚底层知识背景并且确实有必要深入探究指令集细节外,一般不会作为首选方案考虑。故而最不可能成为常用解决办法的就是跟踪汇编码[^3]。 #### 模拟题目四:位运算符特性考察 针对给出的选择题,可以逐一验证各个选项: - `2>>1=1`, `1>>1=0`: 不相同; - `(2>>2)=0`, `(1>>1)=0`: 相同; - `(11^00)=(11)_bin=(3)_dec`, `(1^0)=1`: 不相同; - `~0=-1`(补码表示法下),不是正数1。 综上所述,只有B选项描述成立[(2>>2)和(1>>1)的结果相等][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青岛少儿编程-王老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值