CCF GESP C++ G5核心知识点题库(含解析)
CCF GESP C++ G5核心知识点题库(含解析)
一、初等数论
选择题(4道)
1. 题目:下列关于唯一分解定理的描述,正确的是( )
A. 任意整数都可以分解为素数的乘积
B. 每个合数都可以唯一分解为一系列素数的乘积
C. 两个不同的整数可以分解为相同的素数乘积
D. 以上都不对
答案:B
讲解:唯一分解定理的核心是“每个大于1的自然数(合数或素数)能唯一分解为素数幂乘积(忽略顺序)”。A错误,因为0和1不能分解为素数乘积;C错误,不同整数的素数分解式一定不同(如6=2×3,12=2²×3);B准确描述了合数的唯一分解性质,故选B。
2. 题目:函数fun(int a, int b)的返回值为( )
int fun(int a, int b) {
if (a % b == 0) return b;
else return fun(b, a % b);
}
A. 20 B. 12 C. 4 D. 2(调用fun(20,12))
答案:C
讲解:该函数实现辗转相除法求最大公约数。20%12=8,递归调用fun(12,8);12%8=4,递归调用fun(8,4);8%4=0,返回4,即20和12的最大公约数,故选C。
3. 题目:判断正整数n是否为素数的函数,循环条件应改为( )
bool isPrime(int num) {
if (num < 2) return false;
for (int i = 2; i * i < num; ++i) { // 需修改的循环条件
if (num % i == 0) return false;
}
return true;
}
A. num < 2 改为 num <= 2
B. i * i < num 改为 i * i <= num
C. 循环条件改为 i <= num
D. 循环体中改为 if (num % i != 0)
答案:B
讲解:素数判断的核心是验证是否有小于等于√num的因子。若ii < num,会遗漏i=√num的情况(如num=25,i=5时5×5=25,需纳入判断)。修改为ii <= num可覆盖所有可能因子,故选B。
4. 题目:下列哪种方法不适合判断任意整数N是否为素数?( )
A. 埃氏筛法 B. 线性筛法 C. 二分答案 D. 枚举法
答案:C
讲解:埃氏筛法和线性筛法可高效生成素数表,间接判断N是否为素数;枚举法通过遍历因子直接判断。二分答案适用于“有序区间内找满足条件的最值”,无法直接用于素数判断,故选C。
判断题(4道)
1. 题目:唯一分解定理表明任何一个大于1的整数都可以唯一地分解为素数之和。( )
答案:错误
讲解:唯一分解定理的核心是“素数乘积”而非“素数之和”。例如12=2²×3(乘积形式),而非素数之和的唯一形式,故错误。
2. 题目:找出自然数n以内的所有质数,线性筛法效率高于埃氏筛法。( )
答案:正确
讲解:埃氏筛法可能重复标记同一个合数(如12会被2和3分别标记),而线性筛法保证每个合数仅被其最小质因子标记一次,时间复杂度为O(n),效率更优,故正确。
3. 题目:辗转相除法只能用于求两个正整数的最大公约数,不能用于求最小公倍数。( )
答案:错误
讲解:辗转相除法先求最大公约数gcd(a,b),再通过公式lcm(a,b)=a×b/gcd(a,b)间接求最小公倍数,并非只能求最大公约数,故错误。
4. 题目:小杨设计的拆数程序能将任意非质数自然数N转换成若干个质数的乘积,该程序可实现。( )
答案:正确
讲解:根据唯一分解定理,任何大于1的非质数(合数)都能唯一分解为素数乘积,因此程序可通过质因数分解实现,故正确。
二、(C++)数组模拟高精度加法、减法、乘法、除法
选择题(3道)
1. 题目:下列代码实现高精度加法,横线处应填入( )
string add(string num1, string num2) {
string result;
int carry = 0;
int i = num1.size() - 1, j = num2.size() - 1;
while (i >= 0 || j >= 0 || carry) {
int x = i >= 0 ? num1[i--] - '0' : 0;
int y = j >= 0 ? num2[j--] - '0' : 0;
int sum = x + y + carry;
carry = sum / 10;
// 横线处代码
}
return result;
}
A. result = to_string(sum % 10) + result;
B. result = to_string(carry % 10) + result;
C. result = to_string(sum / 10) + result;
D. result = to_string(sum % 10 + carry) + result;
答案:A
讲解:高精度加法需逐位计算和的个位(sum%10)并逆序拼接(因从低位开始计算),进位更新为sum/10。A符合“个位拼接+逆序存储”逻辑,故选A。
2. 题目:高精度减法函数中,处理借位的代码应填入( )(假设a > b,倒序存储)
vector<int> minus(vector<int> a, vector<int> b) {
vector<int> c;
int len1 = a.size(), len2 = b.size();
for (int i = 0; i < len2; i++) {
if (a[i] < b[i]) { // 借位处理
// 横线处代码
a[i] += 10;
}
c.push_back(a[i] - b[i]);
}
// 处理剩余高位
return c;
}
A. a[i + 1]--; B. a[i]--; C. b[i + 1]--; D. b[i]--;
答案:A
讲解:倒序存储时,a[i]为当前位,a[i+1]为高位。借位需从高位减1,即a[i+1]--,当前位加10,故选A。
3. 题目:关于高精度运算的说法错误的是( )
A. 用于处理超出普通整数范围的大整数运算
B. 高精度乘法的时间复杂度与两个数的位数乘积相关
C. 高精度除法需通过逐位减去除数实现
D. 高精度运算无需处理进位或借位
答案:D
讲解:高精度加法/乘法需处理进位,减法需处理借位,除法需处理余数,D描述错误。A、B、C均为高精度运算的核心特性,故选D。
判断题(3道)
1. 题目:高精度加法的关键是逐位相加并处理进位,减法的关键是逐位相减并处理借位。( )
答案:正确
讲解:高精度运算因无法直接存储大整数,需按位拆分计算,加法核心是进位处理,减法核心是借位处理,故正确。
2. 题目:高精度乘法的运算时间只与参与运算的两个整数中长度较长者的位数有关。( )
答案:错误
讲解:高精度乘法需用第一个数的每一位与第二个数的每一位相乘,时间复杂度为O(n×m)(n、m为两数位数),与两者位数均相关,而非仅与较长者有关,故错误。
3. 题目:数组模拟高精度运算时,通常将数字逆序存储,以便从低位到高位逐位处理。( )
答案:正确
讲解:逆序存储可使低位对应数组下标较小的位置,方便处理进位/借位时向高位(数组下标较大位置)传递,符合运算逻辑,故正确。
三、单链表、双链表、循环链表
选择题(3道)
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. s->next = p->next; p->next->prev = s; s->prev = p; p->next = s;
C. s->prev = p; s->next = p->next; p->next = s; p->next->prev = s;
D. p->next->prev = s; p->next = s; s->prev = p; s->next = p->next;
答案:B
讲解:插入需保证“前驱-后继”关系不中断。步骤为:1. s的后继指向p的原后继;2. p原后继的前驱指向s;3. s的前驱指向p;4. p的后继指向s。B符合逻辑,故选B。
3. 题目:要在双链表头部插入新节点p(head为头指针),横线处应填入( )
void insert(dl_node *head, string my_song) {
dl_node* p = new dl_node;
p->song = my_song;
p->prev = nullptr;
p->next = head;
if (head != nullptr) {
// 横线处代码
}
head = p;
}
A. head->next->prev = p; B. head->next = p; C. head->prev = p; D. 触发空指针异常
答案:C
讲解:双链表头部插入时,若原头节点存在,需将其前驱指向新节点p(保持双向关联)。head->prev = p可建立原头节点与p的反向关联,故选C。
判断题(3道)
1. 题目:单链表只支持在表头进行插入和删除操作。( )
答案:错误
讲解:单链表可在任意位置插入/删除,只需找到目标位置的前驱节点,修改指针即可,并非仅支持表头操作,故错误。
2. 题目:将双向链表的尾节点next指针指向头节点,头节点prev指针指向尾节点,可构成循环链表。( )
答案:正确
讲解:循环链表的核心是“首尾相连”,双向链表通过上述指针修改可实现循环特性,支持从任意节点遍历所有节点,故正确。
3. 题目:链表的存储空间物理上必须连续。( )
答案:错误
讲解:链表节点通过指针关联,物理存储空间可分散,无需连续;数组才要求物理空间连续,故错误。
四、辗转相除法(欧几里得算法)
选择题(3道)
1. 题目:下列函数计算的是a和b的( )
int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
A. 最小公倍数 B. 最大公共质因子 C. 最大公约数 D. 最小公共质因子
答案:C
讲解:该函数是辗转相除法的迭代实现,核心逻辑是“用较大数对较小数取余,迭代至余数为0,此时的除数为最大公约数”,故选C。
2. 题目:递归实现的辗转相除法,说法错误的是( )
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
A. 实现了递归方式 B. 代码简洁,易理解 C. 大数据时占用较多栈空间 D. 执行效率高于迭代版
答案:D
讲解:递归版需频繁调用函数,栈空间开销大,且函数调用有额外开销,执行效率低于迭代版。A、B、C描述正确,D错误。
3. 题目:用辗转相除法求gcd(24,36),调用顺序为( )
A. gcd(24,36)→gcd(24,12)→gcd(12,0)
B. gcd(24,36)→gcd(12,24)→gcd(0,12)
C. gcd(24,36)→gcd(24,12)
D. gcd(24,36)→gcd(12,24)
答案:A
讲解:gcd(24,36)中,36%24=12,递归调用gcd(24,12);24%12=0,递归调用gcd(12,0),返回12。调用顺序为A,故选A。
判断题(3道)
1. 题目:辗转相除法仅适用于正整数,不适用于负整数。( )
答案:错误
讲解:可先对负整数取绝对值,再用辗转相除法计算,结果与正负无关,故可用于负整数,错误。
2. 题目:若gcd(a,b)=d,则存在整数x、y使得ax+by=d。( )
答案:正确
讲解:这是贝祖定理的核心内容,辗转相除法的扩展的可用于求解x、y,故正确。
3. 题目:辗转相除法的时间复杂度与两个数的大小成正比。( )
答案:错误
讲解:辗转相除法的时间复杂度为O(log min(a,b)),与数的位数相关,而非大小成正比,故错误。
五、素数表的埃氏筛法和线性筛法
选择题(4道)
1. 题目:埃氏筛法筛选小于等于n的素数,最外层循环应遍历的范围是( )
vector<int> sieveOfEratosthenes(int n) {
vector<bool> isPrime(n + 1, true);
vector<int> primes;
// 最外层循环
for (int i = 2; i <= sqrt(n); ++i) {
if (isPrime[i]) {
primes.push_back(i);
for (int j = i * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
// 收集剩余素数
return primes;
}
A. i从2到n B. i从1到n-1 C. i从2到sqrt(n) D. i从1到sqrt(n)
答案:C
讲解:埃氏筛法的核心是“标记每个素数的倍数为合数”。若i超过sqrt(n),其倍数已被更小的素数标记,无需重复处理,故最外层循环遍历2到sqrt(n),选C。
2. 题目:线性筛法中,横线处应填入的代码是( )
vector<int> linear_sieve(int n) {
vector<bool> is_prime(n + 1, true);
vector<int> 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
讲解:线性筛法需遍历已找到的素数,标记i与素数的乘积为合数。循环条件需保证“乘积不超过n”且“遍历所有已存素数”,A符合要求,故选A。
3. 题目:关于埃氏筛法和线性筛法的比较,错误的是( )
A. 埃氏筛法可能重复标记同一个合数
B. 线性筛法的时间复杂度为O(n)
C. 线性筛法每个合数仅被其最小质因子标记一次
D. 埃氏筛法实现简单,对于n≤10⁷,效率常高于线性筛法
答案:D
讲解:埃氏筛法因重复标记,时间复杂度为O(n log log n),线性筛法为O(n)。对于n≤10⁷,线性筛法效率更高,D描述错误,故选D。
4. 题目:埃氏筛法中,标记合数的内层循环起始值应为( )
A. i B. i+1 C. i×2 D. i×i
答案:D
讲解:i的倍数中,小于i×i的部分已被更小的素数标记(如i=5,5×2=10已被2标记,5×3=15已被3标记),从i×i开始标记可减少重复操作,故选D。
判断题(4道)
1. 题目:埃氏筛法和线性筛法的时间复杂度都是O(n log log n)。( )
答案:错误
讲解:埃氏筛法时间复杂度为O(n log log n),线性筛法为O(n),因后者避免了重复标记,故错误。
2. 题目:线性筛法中,当i % primes[j] == 0时break,是为了保证每个合数仅被其最小质因子标记。( )
答案:正确
讲解:若i % primes[j] == 0,说明primes[j]是i的最小质因子,此时i×primes[j]的最小质因子也是primes[j],后续更大素数与i的乘积会被更小质因子标记,故break避免重复,正确。
3. 题目:筛法只能用于生成素数表,不能直接判断单个数字是否为素数。( )
答案:错误
讲解:生成素数表后,可通过查询表中对应位置的标记(true/false)直接判断单个数字是否为素数,故错误。
4. 题目:埃氏筛法的空间复杂度为O(n),因为需要存储大小为n+1的标记数组。( )
答案:正确
讲解:埃氏筛法需创建一个长度为n+1的布尔数组标记素数,空间复杂度为O(n),故正确。
六、唯一分解定理
选择题(3道)
1. 题目:下列整数的唯一分解正确的是( )
A. 18=3×6 B. 28=4×7 C. 36=2×3×6 D. 30=2×3×5
答案:D
讲解:唯一分解定理要求分解为素数乘积。A中6不是素数,B中4不是素数,C中6不是素数,D中2、3、5均为素数,分解唯一,故选D。
2. 题目:将n的所有质因子找出,横线处应填入的代码是( )
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. for (int i = 3; i <= n; i++)
B. for (int i = 3; i * i <= n; i++)
C. for (int i = 3; i <= n; i += 2)
D. for (int i = 3; i * i <= n; i += 2)
答案:D
讲解:排除偶数后,只需遍历奇数因子。i×i <= n可减少循环次数,i += 2保证只遍历奇数,符合唯一分解定理的质因子提取逻辑,故选D。
3. 题目:根据唯一分解定理,n=12的质因子分解式为( )
A. 2×2×3 B. 4×3 C. 2×6 D. 12=2²×3
答案:D
讲解:唯一分解定理要求按素数幂形式表示,12的质因子为2(出现2次)和3(出现1次),规范表示为2²×3,故选D。
判断题(3道)
1. 题目:每个大于1的自然数都可以分解成若干个不同的质数的乘积,且分解方式唯一。( )
答案:错误
讲解:唯一分解定理允许质因子重复(如12=2²×3),并非“不同的质数”,故错误。
2. 题目:1不能分解为素数乘积,因此不满足唯一分解定理。( )
答案:正确
讲解:唯一分解定理的适用范围是“大于1的自然数”,1没有质因子,无法分解为素数乘积,故不满足,正确。
3. 题目:根据唯一分解定理,两个不同自然数的质因子分解式一定不同。( )
答案:正确
讲解:唯一分解定理的“唯一性”意味着每个自然数对应唯一的质因子分解式,不同自然数的分解式必然不同,故正确。
七、二分查找/二分答案(二分枚举法)
选择题(4道)
1. 题目:在有序数组[1,3,6,9,17,31,39,52,61,79,81,90,96]中查找82,二分查找的循环次数为( )
A. 2 B. 3 C. 4 D. 5
答案:C
讲解:查找过程:
- left=0,right=12,mid=6(值39),82>39→left=7;
- mid=(7+12)/2=9(值79),82>79→left=10;
- mid=(10+12)/2=11(值90),82<90→right=10;
- mid=10(值81),82>81→left=11,left>right,循环结束。共4次,故选C。
2. 题目:二分查找函数中,横线处应填入的代码是( )
int binarySearch(int arr[], int left, int right, int target) {
while (left <= right) {
// 横线处代码
int mid = left + (right - left) / 2;
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
讲解:(left + right)/2可能因left和right过大导致溢出,left + (right - left)/2可避免溢出,且计算结果与前者一致,是更安全的写法,故选A。
3. 题目:二分答案算法的核心适用场景是( )
A. 有序数组中查找目标值
B. 无序数组中查找最大值
C. 存在单调性的问题中求最优解
D. 链表中查找目标节点
答案:C
讲解:二分答案适用于“问题的解存在上下界,且满足单调性”的场景(如“最大的最小”“最小的最大”问题)。A是二分查找的场景,B、D不适用二分思想,故选C。
4. 题目:在升序数组中查找target的左边界,横线处应填入( )
int getLeftBoundary(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (target <= nums[mid]) {
// 横线处代码
right = mid;
} else {
left = mid + 1;
}
}
return nums[left] == target ? left : -1;
}
A. right = mid - 1; B. right = mid; C. right = mid + 1; D. left = mid;
答案:B
讲解:查找左边界需收缩右边界时保留mid(可能为左边界),若target <= nums[mid],右边界更新为mid,而非mid-1,避免错过左边界,故选B。
判断题(4道)
1. 题目:二分查找要求被搜索的序列必须是有序的,否则无法保证正确性。( )
答案:正确
讲解:二分查找的核心是“通过比较中间值缩小一半搜索范围”,无序序列无法保证中间值两侧的大小关系,导致搜索逻辑失效,故正确。
2. 题目:二分查找仅适用于数组,不适用于链表。( )
答案:正确
讲解:二分查找需要随机访问中间元素(O(1)时间),链表的随机访问时间为O(n),无法高效支持二分查找,故正确。
3. 题目:二分答案和二分查找的核心思想相同,都是“分治+单调性”。( )
答案:正确
讲解:两者均通过分治策略缩小搜索范围,且依赖“单调性”(二分查找依赖序列有序,二分答案依赖问题解的单调性),核心思想一致,故正确。
4. 题目:二分查找的时间复杂度为O(n),与线性查找相同。( )
答案:错误
讲解:二分查找每次缩小一半搜索范围,时间复杂度为O(log n),远低于线性查找的O(n),故错误。
八、贪心算法
选择题(3道)
1. 题目:硬币找零问题中,用贪心算法找最少硬币数,核心思想是( )
A. 每次选择面值最小的硬币
B. 每次选择面值最大的硬币
C. 枚举所有可能组合
D. 动态规划求解
答案:B
讲解:贪心算法在硬币找零问题中(硬币面值满足贪心选择性质,如1、5、10元),每次选择最大面值硬币,可快速逼近目标金额,得到最少硬币数,故选B。
2. 题目:电影院排片最多不重叠电影,排序方式应按( )
int maxMovies(vector<vector<int>>& movies) {
sort(movies.begin(), movies.end(), [](const vector<int>& a, const vector<int>& b) {
// 排序逻辑
return a[1] < b[1];
});
// 后续选择逻辑
}
A. 开始时间升序 B. 结束时间升序 C. 时长升序 D. 开始时间降序
答案:B
讲解:贪心策略为“每次选择结束时间最早的电影”,可留出更多时间安排后续电影,故排序应按结束时间升序,选B。
3. 题目:关于贪心算法的说法正确的是( )
A. 总能得到全局最优解
B. 通过每一步选择局部最优解逼近全局最优
C. 不适用于区间调度问题
D. 时间复杂度一定为O(n)
答案:B
讲解:贪心算法的核心是“局部最优→全局最优”,但并非总能得到全局最优(如非标准硬币面值找零)。A错误,C错误(区间调度是贪心典型应用),D错误(时间复杂度因问题而异),故选B。
判断题(3道)
1. 题目:贪心算法通过每一步选择局部最优解,一定能获得全局最优解。( )
答案:错误
讲解:贪心算法仅在问题满足“贪心选择性质”和“最优子结构”时才能得到全局最优,否则可能得到局部最优(如硬币面值为1、3、4,找零6元,贪心选4+1+1=3枚,最优为3+3=2枚),故错误。
2. 题目:分糖果问题(每个孩子分一个糖果,评分高的孩子分更多)可通过贪心算法求解。( )
答案:正确
讲解:分糖果问题可通过“两次贪心”求解:先从左到右保证评分高的孩子糖果更多,再从右到左调整,满足最优子结构和贪心选择性质,故正确。
3. 题目:贪心算法的时间复杂度通常较低,因为无需回溯或枚举所有可能。( )
答案:正确
讲解:贪心算法每一步仅做局部最优选择,无回溯或枚举操作,时间复杂度多为O(n log n)(排序)或O(n),相对较低,故正确。
九、分治算法(归并排序和快速排序)
选择题(4道)
1. 题目:归并排序的基本思想是( )
A. 将数组分成两个子数组,分别排序后合并
B. 选择枢轴元素划分数组
C. 相邻元素比较交换
D. 从后往前冒泡排序
答案:A
讲解:归并排序是分治算法的典型应用,核心步骤为“分(拆分为两个子数组)→治(子数组排序)→合(合并有序子数组)”,故选A。
2. 题目:快速排序中,选择基准元素的方式会影响( )
A. 排序结果 B. 时间复杂度 C. 空间复杂度 D. 排序稳定性
答案:B
讲解:快速排序的时间复杂度依赖基准元素的选择。若基准元素能将数组均匀划分,时间复杂度为O(n log n);若选择不当(如已排序数组选第一个元素),时间复杂度退化为O(n²),故选B。
3. 题目:归并排序的merge函数被调用次数为( )(数组长度为7)
A. 5 B. 6 C. 7 D. 8
答案:B
讲解:归并排序的merge调用次数等于“分治过程中合并的次数”。数组长度7的分治树中,合并次数为6(每层合并次数:3+2+1=6),故选B。
4. 题目:关于快速排序和归并排序的比较,错误的是( )
A. 两者均为分治算法
B. 归并排序是稳定排序,快速排序是不稳定排序
C. 两者的平均时间复杂度均为O(n log n)
D. 归并排序的空间复杂度为O(1)
答案:D
讲解:归并排序需额外空间存储合并后的子数组,空间复杂度为O(n),快速排序的空间复杂度为O(log n)(递归栈),D描述错误,故选D。
判断题(4道)
1. 题目:归并排序的时间复杂度与输入数组是否有序无关,始终为O(n log n)。( )
答案:正确
讲解:归并排序的分治过程固定将数组拆分为两个子数组,合并步骤的时间复杂度固定,不受输入顺序影响,故正确。
2. 题目:快速排序是稳定排序算法。( )
答案:错误
讲解:快速排序的交换操作可能导致相同元素的相对位置改变(如[2,2,1],基准选2,交换后变为[1,2,2],但原始两个2的顺序可能变化),故为不稳定排序,错误。
3. 题目:分治算法的核心思想是“将大问题分解为小问题,解决小问题后合并结果”。( )
答案:正确
讲解:分治算法的三大步骤为“分解、求解、合并”,核心是通过分解简化问题,合并子问题解得到原问题解,故正确。
4. 题目:归并排序的空间复杂度高于快速排序,因为需要额外的合并空间。( )
答案:正确
讲解:归并排序需O(n)的额外空间存储合并后的数组,快速排序的空间复杂度为O(log n)(递归栈),故归并排序空间复杂度更高,正确。
十、递归
选择题(3道)
1. 题目:下列函数采用的是( )方式实现斐波那契数列
int fiboA(int n) {
if (n <= 1) return n;
return fiboA(n - 1) + fiboA(n - 2);
}
A. 迭代 B. 递归 C. 贪心 D. 分治
答案:B
讲解:函数通过调用自身求解子问题(fiboA(n-1)和fiboA(n-2)),符合递归的定义,故选B。
2. 题目:递归函数调用层数过多会引发的错误是( )
A. 栈空间溢出 B. 堆空间溢出 C. 队列空间溢出 D. 内存泄漏
答案:A
讲解:递归调用时,函数栈帧会被压入系统栈,栈空间有限。调用层数过多会导致栈空间耗尽,引发栈溢出错误,故选A。
3. 题目:下列递归函数的返回值为( )(调用fun(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. 210 C. 420 D. 840
答案:C
讲解:计算过程:
fun(7)=7×fun(5)=7×(5×fun(3))=7×5×(3×fun(2))=7×5×3×(2×fun(1))=7×5×3×2×1=420,故选C。
判断题(3道)
1. 题目:所有递归算法都可以转换为迭代算法。( )
答案:正确
讲解:递归的本质是“函数调用栈”,可通过手动模拟栈的入栈、出栈过程,将递归算法转换为迭代算法,故正确。
2. 题目:递归算法的时间复杂度一定高于迭代算法。( )
答案:错误
讲解:递归和迭代的时间复杂度取决于算法逻辑,而非实现方式。例如递归实现的二分查找时间复杂度为O(log n),与迭代版一致,故错误。
3. 题目:递归函数必须有明确的终止条件,否则会导致无限递归。( )
答案:正确
讲解:若无终止条件,递归函数会持续调用自身,直至栈溢出,故必须定义终止条件(如fib函数中n<=1返回n),正确。
十一、算法复杂度的估算(含多项式、指数、对数复杂度)
选择题(3道)
1. 题目:递归实现的斐波那契数列,时间复杂度为( )
A. O(n) B. O(n²) C. O(2ⁿ) D. O(log n)
答案:C
讲解:递归斐波那契数列的递归树存在大量重复计算(如fibo(5)需计算fibo(4)和fibo(3),fibo(4)需计算fibo(3)和fibo(2)),时间复杂度为指数级O(2ⁿ),故选C。
2. 题目:下列算法的时间复杂度为O(n log n)的是( )
A. 冒泡排序 B. 插入排序 C. 归并排序 D. 枚举法
答案:C
讲解:冒泡排序和插入排序的时间复杂度为O(n²),枚举法的时间复杂度为O(n^k)(k为枚举维度),归并排序的时间复杂度为O(n log n),故选C。
3. 题目:算法复杂度估算中,O(log n)的含义是( )
A. 算法执行时间与n成正比
B. 算法执行时间与n的对数成正比
C. 算法执行时间与n的平方成正比
D. 算法执行时间与2的n次方成正比
答案:B
讲解:大O表示法中,O(log n)表示算法执行时间随n的对数增长,常见于二分查找、归并排序等算法,故选B。
判断题(3道)
1. 题目:O(n log n)的时间复杂度优于O(n²),因为n log n的增长速度慢于n²。( )
答案:正确
讲解:当n增大时,log n的增长速度远慢于n,因此n log n的增长速度慢于n²,O(n log n)算法效率更高,正确。
2. 题目:算法的空间复杂度是指算法执行过程中占用的所有存储空间。( )
答案:错误
讲解:空间复杂度特指算法执行过程中“额外占用的存储空间”,不包括输入数据本身占用的空间,故错误。
3. 题目:指数复杂度(如O(2ⁿ))的算法仅适用于小规模数据,大规模数据下无法实用。( )
答案:正确
讲解:指数复杂度的算法执行时间随n呈爆炸式增长(如n=30时2³⁰约为10⁹),大规模数据下执行时间过长,无法实用,正确。
结尾交付物提议
要不要我帮你整理一份CCF GESP C++五级核心知识点速记手册?包含每个分类的核心概念、常用算法模板和易错点总结,方便你快速复习备考。

浙公网安备 33010602011771号