学习C++从娃娃抓起!记录下CCF-GESP备考学习过程中的题目,记录每一个瞬间。
附上汇总贴:GESP编程能力等级认证C++编程真题解析 | 汇总
单选题
第1题
下面关于链表和数组的描述,错误的是( )。
A.数组大小固定,链表大小可动态调整。
B.数组支持随机访问,链表只能顺序访问。
C.存储相同数目的整数,数组比链表所需的内存多。
D.数组插入和删除元素效率低,链表插入和删除元素效率高。
【答案】:C
【解析】
错误的是C。链表由于需要额外存储指针,通常比数组占用更多内存。
第2题
通过( )操作,能完成在双向循环链表结点p之后插入结点s 的功能(其中next 域为结点的直接后继,prev 域为结点的直接前驱)。
A.p->next->prev = s; s->prev = p; p->next = s; s->next = p->next;
B.p->next->prev = s; p->next = s; s->prev = p; s->next = p->next;
C.s->prev = p; s->next = p->next; p->next = s; p->next->prev = s;
D. s->next = p->next; p->next->prev = s; s->prev = p; p->next = s;
【答案】:D
【解析】
选项D正确地更新了双向循环链表的指针。具体步骤如下:
1)s->next = p->next;
将新结点s的后继设为p的后继。
2)p->next->prev = s;
将原本p的后继的前驱设为s。
3)s->prev = p;
将新结点s的前驱设为p。
4)p->next=s;
将p的后继设为s。
这样,所有相关指针都被正确更新,实现插入操作。
第3题
对下面两个函数,说法错误的是( )。
int sumA(int n) {
int res = 0;
for (int i=1; i<=n; i++) {
res += i;
}
return rs;
}
int sumB(int n) {
if (n==1)
return 1;
int res = n + sumB(n-1);
return res;
}
A.sumA体现了迭代的思想。
B.SumB采用的是递归方式。
C.SumB函数比SumA的时间效率更高。
D.两个函数的实现的功能相同。
【答案】:C
【解析】
sumA函数使用迭代方式,通过一个for循环计算1到n的和。
sumB函数采用递归方式,逐步将问题分解为求1到(n-1)的和,再加上n。
由于递归调用会增加函数调用栈的开销,因此sumB的时间效率低于sumA。
两个函数实现了相同的功能,艮即计算从1到n的整数和。
第4题
有如下函数fun,则fun(20, 12)的返回值为( )。
int fun(int a, int b) {
if (a%b==0)
return b;
else
return fun(b, a%b);
}
【答案】:C
【解析】
fun(20, 12) = fun(12, 8) = fun(8, 4) = 4
第5题
下述代码实现素数表的埃拉托斯特尼筛法,筛选出所有小于等于n 的素数,则横线上应填的最佳代码是( )。
void sieve_Eratosthenes(int n) {
vector<bool> is_prime(n+1, true);
vector<int> primes;
for (int i=2; i*i<=n; i++) {
if (is_prime[i]) {
primes.push_back(i);
__________________________ { // 在此处填入代码
is_prime[j] = false;
}
}
}
for (int i=sqrt(n)+1; i<=n; i++) {
if (is_prime[i]) {
primes.push_back(i);
}
}
return primes;
}
A.for (int j=i; j<=n; j++)
B.for (int j=i*i; j<=n; j++)
C.for (int j=i*i; j<=n; j+=i)
D.for (int j=i; j<=n; j+=i)
【答案】:C
【解析】
埃拉托斯特尼筛法用于找出所有小于等于n的素数。代码中需要填入的是将i的倍数标记为非素数的循环。根据第9行代码可知,j代表i的倍数。
选项A和B中的j代表的不是i的倍数。
选项D错误的将i素数本身标记为非素数。
选项C更符合逻辑,确保j从i*i开始,将每个i的倍数标记为非素数。
因此,最佳选择是C。
第6题
下述代码实现素数表的线性筛法,筛选出所有小于等于 n 的素数,则横线上应填的代码是( )。
vector<int> sieve_linear(int n) {
vector<bool> is_prime(n+1, true);
vector<int> primes;
for (int i=2; i<=n/2; i++) {
if (is_prime[i])
primes.push_back(i);
___________________________ { // 在此处填入代码
is_prime[i * primes[j]] = 0;
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.for (int j=0; j<primes.size() && i*primes[j]<=n; j++)
B.for (int j=1; j<primes.size() && i*j<=n; j++)
C.for (int j=2; j<primes.size() && i*primes[j]<=n; j++)
D.以上都不对
【答案】:A
【解析】
该代码实现了线性筛法,用于找出所有小于等于n的素数。关键在于内层循环,确保将i的倍数标记为非素数。
选项A中的for (int j=0; j<primes.size() && i*primes[j]<=n; j++)
正确地初始化j,并且条件判断合理,确保不会越界。
内部逻辑:is_prime[i * primes[j]]=0;
将i与当前质数的乘积标记为非素数;如果i能被当前质数整除,则跳出循环。
因此,最佳选择是A。
第7题
下面函数可以将n 的所有质因数找出来,其时间复杂度是( )。
#include <iostream>
#include <vector>
vector<int> get_prime_factors(int n) {
vector<int> factors;
while (n%2==0) {
factors.push_back(2);
n /= 2;
}
for (int i=3; i*i<=n; i+=2) {
while (n%i==0) {
factors.push_back(i);
n /= i;
}
}
if (n>2) {
factors.push_back(n);
}
return factors;
}
A. O ( n 2 ) O(n^2) O(n2)
B. O ( n l o g n ) O(nlogn) O(nlogn)
C. O ( n l o g n ) O(\sqrt nlogn) O(nlogn)
D. O ( n ) O(n) O(n)
【答案】:C
【解析】
该函数用于找出整数n的所有质因数。时间复杂度分析如下:
首先,处理2的倍数,通过while(n % 2 == 0)
循环将所有2的因子加入结果中。这部分操作最多执行
O
(
l
o
g
n
)
O(logn)
O(logn)次,因为每次都将n除以2。
接下来,通过一个for循环从3开始检查奇数因子。内层while(n % i == 0)
循环在找到一个因子i时,将其不断地除去。这部分操作的总次数与n的素因子的个数有关。由于质因数分解的过程涉及到对每个可能的因子进行试探,最坏情况下需要检查到sqrt(n),因此外层for循环的复杂度为
O
(
s
q
r
t
(
n
)
)
O(sqrt(n))
O(sqrt(n))。结合内层while循环,每次除法操作的复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn)。
综合起来,总体时间复杂度为 O ( s q r t ( n ) ∗ l o g n ) O(sqrt(n)*logn) O(sqrt(n)∗logn)。因此,正确答案是C。
第8题
现在用如下代码来计算 x n x^n xn(n个x相乘),其时间复杂度为( )。
double quick_power(double x, unsigned n) {
if (n==0) return 1;
if (n==1) return x;
return quick_power(x, n/2) * quick_power(x, n/2) * ((n & 1) ? x : 1);
}
A. O ( n ) O(n) O(n)
B. O ( n 2 ) O(n^2) O(n2)
C. O ( l o g n ) O(logn) O(logn)
D. O ( n l o g n ) O(nlogn) O(nlogn)
【答案】:A
【解析】
这段代码采用快速幂算法,用于计算 x n x^n xn。正确的快速幂算法的时间复杂度为 O ( l o g n ) O(logn) O(logn),但是本题中的实现,每每次递归调用会进行两次 quick_power 调用,因此时间复杂度为 O ( 2 l o g n ) O(2^{logn}) O(2logn),即 O ( n ) O(n) O(n)。
第9题
假设快速排序算法的输入是一个长度为n的已排序数组,且该快速排序算法在分治过程总是选择第一个元素作为基准元素。下面选项( )描述的是在这种情况下的快速排序行为。
A.快速排序对于此类输入的表现最好,因为数组已经排序。
B.快速排序对于此类输入的时间复杂度是 O ( n l o g n ) O(n log n) O(nlogn)。
C.快速排序对于此类输入的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。
D.快速排序无法对此类数组进行排序,因为数组已经排序。
【答案】:C
【解析】
快速排序在已排序数组上选择第一个元素作为基准时,每次划分都会导致一侧为空,另一侧包含剩余的所有元素。这种情况下,递归深度为n,且每层需要 O ( n ) O(n) O(n)时间,因此总时间复杂度为 O ( n 2 ) O(n^2) O(n2)。选项C正确。
第10题
考虑以下C++代码实现的归并排序算法:
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int L[n1], R[n2];
for (int i=0; i<n1; i++)
L[i] = arr[left+i];
for (int j=0; j<n2; j++)
R[j] = arr[mid+1+j];
int i=0, j=0, k=left;
while (i<n1 && j<n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
}
else {
arr[k] = R[j];
j++;
}
k++;
}
while (i<n1) {
arr[k] = L[i];
i++;
k++;
}
while (j<n2) {
arr[k] = R[j];
j++;
k++;
}
}
void merger_sort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
merge_sort(arr, left, mid);
merge_sort(arr, mid+1, right);
merge_sort(arr, left, mid, right);
}
}
对长度为n的数组arr,调用函数merge_sort(a, 0, n-1), 在排序过程中merge 函数的递归调用次数大约是( )。
A. O ( 1 ) O(1) O(1)
B. O ( n ) O(n) O(n)
C. O ( l o g n ) O(log n) O(logn)
D. O ( n l o g n ) O(n log n) O(nlogn)
【答案】:B
【解析】
本题中询问的是merge函数递归调用的次数,归并排序是一个分治算法,它将数组不断二分,直到每个子数组只有一个元素,即有n个子数组,合并过程中将这些数组两两合并直到合并成一个数组,排序完成,因此合并了n-1次,大约是 O ( n ) O(n) O(n)。
第11题
现在有n个人要过河,每只船最多载2人,船的承重为100kg。下列代码中,数组weight 中保存有n个人的体重(单位为kg),已经按从小到大排好序,代码输出过河所需要的船的数目,采用的思想为( )。
int i, j;
int count = 0;
for (i=0, j=n-1; i<j; j--) {
if (weight[i] + weight[j] <= 100) {
i++;
}
count++;
}
printf("过河的船数: %d\n", count);
A.枚举算法
B.贪心算法
C.迭代算法
D.递归算法
【答案】:B
【解析】
代码实现的方法每次都尽量让最轻和最重的人一起过河,以减少船的数量。这种策略是典型的贪心算法,因为它在每一步选择中都做出局部最优解,从而达到全局最优。因此,答案为B。
第12题
关于分治算法,以下哪个说法正确?
A.分治算法将问题分成子问题,然后分别解决子问题,最后合并结果。
B.归并排序不是分治算法的应用。
C.分治算法通常用于解决小规模问题。
D.分治算法的时间复杂度总是优于 O ( n l o g ( n ) ) O(n log(n)) O(nlog(n))。
【答案】:A
【解析】
A选项正确。分治算法的核心思想是将一个复杂的问题分解为相对简单的子问题,分别求解后再合并其结果。
B选项错误。归并排序正是分治算法的经典应用之一。
C选项错误。分治算法通常用于大规模问题,通过递归地分解问题来简化计算。
D选项错误。分治算法的时间复杂度具体取决于问题和实现方式。
第13题
根据下述二分查找法,在排好序的数组1,3,6,9,17,31,39,52,61,79 中查找数值31,循环while (left <= right) 执行的次数为( )。
int binary_search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] < target) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return -1; // 如果找不到目标元素,返回-1
}
A.1
B.2
C.3
D.4
【答案】:C
【解析】
具体步骤如下:
1)初始:left = 0,right = 9,mid = 4(元素为17)
2)更新:left = 5,right = 9,mid = 7(元素为52)
3)更新:left = 5,right = 6,mid = 5(元素为31)
共执行了3次循环,因此答案是C。
第14题
以下关于高精度运算的说法错误的是( )。
A.高精度计算主要是用来处理大整数或需要保留多位小数的运算。
B.大整数除以小整数的处理的步骤可以是,将被除数和除数对齐,从左到右逐位尝试将除数乘以某个数,通过减法得到新的被除数,并累加商。
C.高精度乘法的运算时间只与参与运算的两个整数中长度较长者的位数有关。
D.高精度加法运算的关键在于逐位相加并处理进位。
【答案】:C
【解析】
A选项正确。
B选项正确。
C选项错误。高精度乘法的运算时间与两个整数的长度都有关,而不仅仅是较长者的位数。
D选项正确。
第15题
当n=7时,下面函数的返回值为( )。
int fun(int n) {
if (n==1) return 1;
else if (n>=5) return n * fun(n-2);
else return n * fun(n-1);
A.105
B.840
C.210
D.420
【答案】:C
【解析】
fun(1) = 1
fun(2) = 2 * fun(1) = 2 * 1 = 2
fun(3) = 3 * fun(2) = 3 * 2 = 6
fun(5) = 5 * fun(3) = 5 * 6 = 30
fun(7) = 7 * fun(5) = 7 * 30 = 210
判断题
第16题
在操作系统中,需要对一组进程进行循环。每个进程被赋予一个时间片,当时间片用完时,CPU将切换到下一个进程。这种循环操作可以通过环形链表来实现。
A.正确
B.错误
【答案】:A
【解析】
在操作系统中,时间片轮转调度算法(Round Robin)常用于实现多任务处理。环形链表是一种适合的结构,因为它可以自然地循环遍历所有进程,当一个进程的时间片用完时,CPU可以迅速切换到下一个进程。因此,这种循环操作确实可以通过环形链表来实现。
第17题
找出自然数n以内的所有质数,常用算法有埃拉托斯特尼(埃氏)筛法和线性筛法,其中线性筛法效率更高。
A.正确
B.错误
【答案】:A
【解析】
埃拉托斯特尼筛法和线性筛法都是用于找出自然数n以内所有质数的有效算法。埃氏筛法通过标记合数来找到质数,而线性筛法在此基础上进行了优化,避免了重复标记,从而提高了效率。因此,线性筛法确实比埃氏筛法更高效。
第18题
唯一分解定理表明任何一个大于1的整数都可以唯一地分解为素数之和。
A.正确
B.错误
【答案】:B
唯一分解定理(也称为算术基本定理)表明,任何一个大于1的整数都可以唯一地表示为若干个素数的乘积,而不是和。例如,30可以唯一分解为(2×3×5)。题目中提到“素数之和”是错误的。
第19题
贪心算法通过每一步选择局部最优解,从而一定能获得最优解。
A.正确
B.错误
【答案】:B
【解析】
贪心算法通过每一步选择局部最优解,但并不总能保证得到全局最优解。只有在某些特定问题中,贪心算法才能确保获得最优解。因此,题目中的说法是不正确的。
第20题
快速排序和归并排序的平均时间复杂度均为 O ( n l o g n ) O(nlogn) O(nlogn),且都是稳定排序。
A.正确
B.错误
【答案】:B
【解析】
快速排序和归并排序的平均时间复杂度均为( O ( n l o g n ) O(n log n) O(nlogn)),但它们在稳定性上有所不同。归并排序是稳定排序,而快速排序不是稳定排序,因为元素相同的两个记录可能会因为交换而改变顺序。
第21题
插入排序的时间复杂度总是比快速排序低。
A.正确
B.错误
【答案】:B
【解析】
插入排序的时间复杂度在最坏情况下为 O ( n 2 ) O(n^2) O(n2),而快速排序的平均时间复杂度为 O ( n l o g n ) O(n log n) O(nlogn)。虽然插入排序在小规模数据或接近有序的数据上表现较好,但总体来说,快速排序在大多数情况下更高效。因此,题目中的说法是不正确的。
第22题
第7题引入分治策略往往可以提升算法效率。一方面,分治策略减少了操作数量;另一方面,分治后有利于系统的并行优化。
A.正确
B.错误
【答案】:A
【解析】
分治策略通过将问题分解为更小的子问题,逐步解决并合并结果,从而提升算法效率。它不仅减少了操作数量,还能进行并行计算,提高系统性能。因此,引入分治策略确实可以提升算法效率。
第23题
二分查找要求被搜索的序列是有序的,否则无法保证正确性。
A.正确
B.错误
【答案】:A
【解析】
二分查找算法要求被搜索的序列必须是有序的,因为它通过不断折半缩小搜索范围来定位目标值。如果序列无序,二分查找无法正确判断目标值的位置,从而不能保证正确性。
第24题
在C++语言中,递归的实现方式通常会占用更多的栈空间,可能导致栈溢出。
A.正确
B.错误
【答案】:A
【解析】
在C++语言中,递归函数调用会占用栈空间,每次递归调用都会在栈上分配新的帧。如果递归深度过大,可能导致栈溢出。因此,递归实现方式确实通常会占用更多的栈空间,有潜在的栈溢出风险。
第25题
对于已经定义好的标准数学函数sin(x),应用程序中的语句y=sin(sin(x));是一种递归调用。
A.正确
B.错误
【答案】:B
【解析】
递归调用是指函数在其定义中直接或间接地调用自身。语句 y = sin(sin(x));是对标准数学函数 sin 的嵌套调用,而不是递归调用,因为 sin 函数并没有在其内部再次调用自己。
编程题
第26题 小杨的武器
【题目描述】
小杨有 n n n种不同的武器,他对第 i i i 种武器的初始熟练度为 c i c_i ci。
小杨会依次参加 m m m 场战斗,每场战斗小杨只能且必须选择一种武器使用。假设小杨使用了第 i i i种武器参加了第 j j j场战斗,战斗前该武器的熟练度为 c i ′ c'_i ci′,则战斗后小杨对该武器的熟练度会变为 c i ′ + a j c'_i+a_j ci′+aj。需要注意的是 a j a_j aj可能是正数、0或负数,这意味着小杨参加战斗后对武器的熟练度可能会提高,也可能会不变,还有可能降低。
小杨想请你编写程序帮助他计算出如何选择武器才能使得 m m m 场战斗后,自已对 n n n种武器的熟练度的最大值尽可能大。
【输入】
第一行包含两个正整数 n n n, m m m,含义如题面所示。
第二行包含 n n n个正整数 c 1 , c 2 , … , c n c_1,c_2,\dots,c_n c1,c2,…,cn,代表小杨对武器的初始熟练度。
第三行包含 m m m个正整数 a , a 2 , … , a m a_,a_2,\dots, a_m a,a2,…,am,代表每场战斗后武器熟练度的变化值。
【输出】
输出一个整数,代表 m m m 场场战斗后小杨对 n n n种武器的熟练度的最大值最大是多少。
【输入样例】
2 2
9 9
1 -1
【输出样例】
10
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 100010; // 定义数组大小常量
int a[N], c[N]; // 定义两个数组,一个存储每场战斗的增益值,一个存储初始熟练度
int main()
{
int n, m; // 定义变量n和m,分别表示武器种类数和战斗次数
cin >> n >> m; // 输入n和m
int mx = -10000; // 初始化最大熟练度为一个较小的值
for (int i=1; i<=n; i++) { // 循环输入每种武器的初始熟练度
cin >> c[i];
mx = max(mx, c[i]); // 更新当前最大熟练度
}
for (int i=1; i<=m; i++) { // 循环输入每场战斗的增益值
cin >> a[i];
}
for (int i=1; i<=m; i++) { // 根据战斗增益更新最大熟练度
if (n==1 || a[i]>0) { // 如果只有一种武器或者当前战斗增益为正
mx += a[i]; // 增加最大熟练度
}
}
cout << mx << endl; // 输出最终的最大熟练度
return 0;
}
【运行结果】
2 2
9 9
1 -1
10
第27题 挑战怪物
【题目描述】
小杨正在和一个怪物战斗,怪物的血量为 h h h,只有当怪物的血量恰好为 0 0 0时小杨才能够成功击败怪物。
小杨有两种攻击怪物的方式:
- 物理攻击。假设当前为小杨第 i i i次使用物理攻击,则会对怪物造成 2 i − 1 2^{i-1} 2i−1点伤害。
- 魔法攻击。小杨选择任意一个质数 x x x( x x x不能超过怪物当前血量),对怪物造成 x x x 点伤害。由于小杨并不擅长魔法,他只能使用至多一次魔法攻击。
小杨想知道自己能否击败怪物,如果能,小杨想知道自己最少需要多少次攻击。
【输入】
第一行包含一个正整数 t t t,代表测试用例组数。
接下来是 t t t组测试用例。对于每组测试用例,第一行包含一个正整数 h h h,代表怪物血量。
【输出】
对于每组测试用例,如果小杨能够击败怪物,输出一个整数,代表小杨需要的最少攻击次数,如果不能击败怪物,输出 − 1 -1 −1。
【输入样例】
3
6
188
9999
【输出样例】
2
4
-1
【代码详解】
#include <bits/stdc++.h>
using namespace std;
vector<int> prime; // 用于存储所有素数
bool is_prime[100010]; // 标记数组,用于判断一个数是否为素数
// 埃氏筛法,生成从2到n的所有素数
void Eratosthenes(int n) {
memset(is_prime, true, sizeof(is_prime)); // 初始化标记数组,全设为true(假定全是素数)
is_prime[0] = is_prime[1] = false; // 0和1不是素数
for (int i=2; i<=n; i++) {
if (is_prime[i]) { // 如果当前数字是素数
prime.push_back(i); // 将其加入素数列表
for (int j=2*i; j<=n; j+=i) // 将该素数的倍数全部标记为非素数
is_prime[j] = false;
}
}
}
int main()
{
Eratosthenes(100000); // 生成最多到100000的所有素数
int t; // 测试用例数量
cin >> t;
while (t--) {
int tmp = 1; // 初始攻击力
int x; // 怪物的血量
cin >> x;
int ans = 0; // 攻击次数
while (true) {
if (is_prime[x]) { // 如果当前血量是素数
ans++; // 增加一次攻击
break; // 结束循环
}
x -= tmp; // 使用物理攻击,减少血量
ans++; // 增加一次攻击次数
if (x<=0) { // 如果血量小于等于0
if (x<0) ans = -1; // 如果血量小于0,设置结果为-1表示失败
break; // 结束循环
}
tmp *= 2; // 每次攻击后,攻击力翻倍
}
cout << ans << endl; // 输出结果
}
return 0;
}
【运行结果】
3
6
2
188
4
9999
-1