2022年CSP-J-初赛试卷及解析
2024年7月6日 | 分类: 【编程】
参考:https://blog.csdn.net/lq1990717/article/details/126971665
参考:https://www.cnblogs.com/hellohebin/p/16709315.html
一、单项选择题(共 15 题,每题 2 分,共计 30 分;每题有且仅有一个正确选项)
1. 以下哪种功能没有涉及 C++ 语言的面向对象特性支持:( )。
A. C++ 中调用 printf 函数
B. C++ 中调用用户定义的类成员函数
C. C++ 中构造一个 class 或 struct
D. C++ 中构造来源于同一基类的多个派生类
答案:A。
解析:
printf是C语言中就可以使用的函数
而c++中定义类或结构体,对象调用成员函数,构造派生类,都是面向对象语言才能支持的操作。
面向对象(oritend-object,OO):以问题根源作为关注点:人,饭
面向过程(oritend-process,OP):以问题本身作为关注点:吃饭
printf是C语言中的一个输出函数,并不涉及到OOP特性,只要涉及到类(class)都是属于OOP特性。
面向对象编程中具有的三大特性:
封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
继承:将可复用的类作为基类(父类),子类继承父类,提高代码复用性。
多态:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。
2. 有 6 个元素,按照 6 、 5 、 4 、 3 、 2 、 1 的顺序进入栈 S ,请问下列哪个出栈序列是非法的( )。
A. 5 4 3 6 1 2
B. 4 5 3 1 2 6
C. 3 4 6 5 2 1
D. 2 3 4 1 5 6
答案:C。
解析:入栈出栈序列问题,
如果数值a出栈,那么在a前入栈的元素要么已出栈,要么顺序地排列在栈中。
C选项中,当4出栈时,4前入栈的6,5一定都在栈中,情况为:栈底-6-5。所以接下来不可能是6出栈,只能是5出栈。
栈:后进先出表(LIFO)
找规律:入栈降序序,出栈如果降序或者连续升序合法;当陡升时,元素应当为栈内最小值,四个选项中陡升情况如下。
A. 5 4 [3 6] 1 2;其中6是栈内最小值,合法。
B. 4 5 3 1 2 6
C. 3 [4 6] 5 2 1;其中6不是栈内最小值,应当为5。
D. 2 3 4 1 5 6
3. 运行以下代码片段的行为是( )。
int x = 101;
int y = 201;
int *p = &x;
int *q = &y;
p = q;
A. 将 x 的值赋为 201
B. 将 y 的值赋为 101
C. 将 q 指向 x 的地址
D. 将 p 指向 y 的地址
答案:D。
解析:
p是指向x的指针,也就是x的地址。
q是指向y的指针,也就是y的地址。
把q赋值给p,也就是让p从指向x的指针变为指向y的指针。
4. 链表和数组的区别包括( )。
A. 数组不能排序,链表可以
B. 链表比数组能存储更多的信息
C. 数组大小固定,链表大小可动态调整
D. 以上均正确
答案:C。
解析:
选项A:数组和链表都能做排序。比如冒泡排序,里面只有交换相邻元素的操作,这一操作在数组和链表中都可以做。
选项B:链表和数组能存储的信息取决于其长度,哪个更长哪个能存储更多信息。
C选项是正确的。一旦申请数组,数组的长度就是固定的了。而链表可以申请和释放结点,大小可以动态调整。
A. 错误;sort排序用的那么多,肯定错的,数组可以排序,链表可以排序;
理论上,任何数据结构都是可以规定其先后的,也就是排序。
B. 正确;链表比数组能存储更多的信息,我们说引入链表的原因就是因为数组的空间是连续的,而要开辟一段连续的很大的内存空间是不行的,于是可以开辟不连续的内存空间(节点),通过指针来连接各个空间,从而构建了链表。那么说链表比数组能存储更多的信息是正确的。
C. 正确;有人会说vector动态数组可以调整大小,C就错误了。但其实vector的本质是当数组a容量不够时,新建数组b并复制数组a的内容到b,之后销毁a,数组a,b本身的长度是固定的。
链表大小是通过节点数量确定的,而节点数量是可以变化的,所以链表大小可动态调整。
那么问题来说,答案不就选BC了吗?怎么多选了?
其实笔者认为题目稍微有点小问题,如果我是出题人,肯定会给BC都正确。
但是对于B.链表比数组能存储更多的信息,是具有一定的限制条件的,你可以试想一下,一个电脑内存设置空间大小刚好为可开长度为 N 的数组,并且内存已经开辟完了,那么如果使用链表,而链表是需要指针域来占用空间的,所以相比之下,数组的存储空间更大了。
所以建议加上限定条件:当内存一定时,….
5. 对假设栈 S 和队列 Q 的初始状态为空。存在 e1~e6 六个互不相同的数据,每个数据按照进栈 S 、出栈 S 、进队列 Q 、出队列 Q 的顺序操作,不同数据间的操作可能会交错。已知栈 S 中依次有数据 e1 、 e2 、 e3 、 e4 、 e5 和 e6 进栈,队列 Q 依次有数据 e2 、 e4 、 e3 、e6 、 e5 和 e1 出队列。则栈 S 的容量至少是( )个数据。
A. 2
B. 3
C. 4
D. 6
答案:B。
解析:
栈是后进先出,队列是先进先出。
队列出队的顺序,就是队列入队的顺序。而队列入队的顺序,就是栈出栈的顺序。所以该题变为:
已知入栈顺序是:e1,e2,e3,e4,e5,e6,出栈顺序是:e2,e4,e3,e6,e5,e1,请问在整个入栈出栈过程中栈中元素的最大个数。
根据入栈出栈顺序,可知:
操作 |
栈内情况(左侧是栈底) |
出栈序列 |
e1入栈 |
e1 |
|
e2入栈 |
e1,e2 |
|
e2出栈 |
e1 |
e2 |
e3入栈 |
e1,e3 |
e2 |
e4入栈 |
e1,e3,e4 |
e2 |
e4出栈 |
e1,e3 |
e2,e4 |
e3出栈 |
e1 |
e2,e4,e3 |
e5入栈 |
e1,e5 |
e2,e4,e3 |
e6入栈 |
e1,e5,e6 |
e2,e4,e3 |
e6出栈 |
e1,e5 |
e2,e4,e3,e6 |
e5出栈 |
e1 |
e2,e4,e3,e6,e5 |
e1出栈 |
|
e2,e4,e3,e6,e5,e1 |
根据上表可知栈中最大元素数量为3。
6. 对表达式 a+(b-c)*d 的前缀表达式为( ),其中 + 、 – 、 * 是运算符。
A. *+a-bcd
B. +a*-bcd
C. abc-d*+
D. abc-+d
答案:B。
解析:
中缀表达式转前缀表达式。先运算b-c,变为-bc。然后是X*d(X为b-c),变为*Xd。把X的前缀表达式代入,为*-bcd。最后是a+X(X为(b-c)*d),变为+aX,把X的前缀表达式代入,为+a*-bcd。
中缀表达式:a+b、前缀表达式:+ab、后缀表达式:ab+。
对于这个问题,我们可以将其中的某一部分看作一个整体,如 a+(b-c)*d 中(b-c)应当为一个数 x,所以对其变前缀就是 x=(-bc)。
原式就变为 a+x*d -> a+(*xd) -> +a(*xd) -> +a*-bcd。
这里可以结合二叉树的遍历方式一起回忆。
7. 假设字母表 {a, b, c, d, e} 在字符串出现的频率分别为 10%, 15%, 30%, 16%,29% 。若使用哈夫曼编码方式对字母进行不定长的二进制编码,字母 d 的编码长度为( )位。
A. 1
B. 2
C. 2 或 3
D. 3
答案:B
【解析1】:
考察哈夫曼树和哈夫曼编码。构建哈夫曼树的方法为:每次选取两个权值最小的结点,加上双亲结点构成一棵树。
初始一共有5个结点,每个结点的权值分别为:a:10,b:15,c:30,d:16,e:29
选择权值最小的两个结点a和b,设结点f是a、b的双亲,权值25。
选择权值最小的两个结点f和d,设结点g是f、d的双亲,权值41。
选择权值最小的两个结点c和e,设结点h是c、e的双亲,权值59。
选择权值最小的两个结点g和h,设结点i是g、h的双亲,权值100。
构成的树如下图所示。
在哈夫曼树中,从根结点开始,每向下走一层编码多1位。根据构造出来的哈夫曼树可知,d的编码是两位。
【解析2】
哈夫曼编码基于信源的概率统计模型,它的基本思路是出现概率大的信源符号编短码,出现概率小的信源符号编长码,从而使平均码长最小。这是一种贪心策略,每次选取当前概率最大的符号使用现有的最短码。
构建:选择最小权值与次小权值组成一棵树,最小在左,并将其加入集合。
编码:规定哈夫曼树中的左分支为0,右分支为1,则从根结点到每个叶结点所经过的分支对应的 0和 1组成的序列便为该结点对应字符的编码。
8. 一棵有 n 个结点的完全二叉树用数组进行存储与表示,已知根结点存储在数组的第 1 个位置。若存储在数组第9个位置的结点存在兄弟结点和两个子结点,则它的兄弟结点和右子结点的位置分别是( )。
A. 8 、 18
B. 10 、 18
C. 8 、 19
D. 10 、 19
答案:C
解析:
二叉树的顺序存储结构中,第i结点的左孩子的下标为2*i,右孩子的下标为2*i+1,双亲的下
标为i/2。
因此第9位置结点一定是第4结点的右孩子(左孩子下标都是偶数,右孩子下标都是奇数)。
该结点的兄弟结点是4的左孩子,在数组中的下标应该比9少1,为8。
9的右孩子的下标为 9*2+1=19。
9. 考虑由N个顶点构成的有向连通图,采用邻接矩阵的数据结构表示时,该矩阵中至少存在( )个非零元素。
A. N-1
B. N
C. N+1
D. N^2
答案:A。
解析:
有向连通图,分为强连通图、单向连通图、弱连通图。若把有向边都当做无向边,如果这个无向图是连通图,那么这个图是弱连通图。
n个顶点的无向图,最少有n-1条边。那么这个有向图中最少有n-1条边,就可以构成弱连通图。有向图中每条边在邻接矩阵中就是一个元素,占一个位置,因此至少存在n-1个非零元素。
答案:B
解析:笔者认为描述不明确(权值无明确初始值,有向连通图表述不清楚)。
若从顶点 i 到顶点 j 有路径,则称顶点 i 和 j 是连通的。
若无向图 G 中任意两个顶点都连通,则称为连通图,否则称为非连通图。
若有向图 G 中任意两个顶点都连通,则称为强连通图。
有向图 G 中的极大强连通子图称为 G 的强连通分量。
思路:构造一个 N 个点的有向连通图,一个圆环 N 条边。
但是还没有加 a[i][i]=1 和特判 N=1 的情况,我认为这个题目有争议!
10. 以下对数据结构的表述不恰当的一项为:( )。
A. 图的深度优先遍历算法常使用的数据结构为栈。
B. 栈的访问原则为后进先出,队列的访问原则是先进先出。
C. 队列常常被用于广度优先搜索算法。
D. 栈与队列存在本质不同,无法用栈实现队列。
答案:D
解析:
选项A:图的深度优先遍历算法经常用递归来完成,而递归实际是利用了C++中的函数递归调用栈,本质上是使用了栈的结构。实际上,如果直接使用栈,也可以完成图的深度优先遍历。
选项B、C都是正确的表述。
选项D中,栈与队列本质上都是功能受限的线性表,本质是相同的。用栈实现队列,虽然平时不会这样做,这样做也没什么意义,但还是可以实现的。
设栈s1与s2来实现一个队列的功能:
入队:元素入栈s1
出队:如果栈s2不为空,那么栈s2出栈。如果s2为空,那么把s1中的所有元素出栈并入栈到s2,而后s2出栈。
11. 以下哪组操作能完成在双向循环链表结点 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。
解析:
观察选项可知,四个选项为这四个语句的不同排列:
s->prev=p;
s->next=p->next;
p->next=s;
p->next->prev=s;
这里发生变化,又可能给其它量赋值的就是p->next。
使p->next发生变化的语句为:p->next=s;
而s->next=p->next;与p->next->prev=s;中用到的都应该是变化前的p->next,指向的是原来p的下一个结点。
所以p->next=s;应该放在最后,选D。
12. 以下排序算法的常见实现中,哪个选项的说法是错误的:( )。
A. 冒泡排序算法是稳定的
B. 简单选择排序是稳定的
C. 简单插入排序是稳定的
D. 归并排序算法是稳定的
答案:B
解析:
考察排序的稳定性。选择排序是不稳定的,冒泡、插入、归并都是稳定的。
算法大全:
13. 八进制数 32.1 对应的十进制数是( )。
A. 24.125
B. 24.250
C. 26.125
D. 26.250
答案:C
解析:
进制转换问题,八进制转十进制的方法为:按位权展开。
整数部分: \({3}\ast{8^1}+{2}\ast{8^0}=26\)
小数部分: \({1}\ast{8^{-1}}=0.125\)
因此八进制 32.1 为十进制 26.125 ,选C。
14. 一个字符串中任意个连续的字符组成的子序列称为该字符串的子串,则字符串 abcab 有( )个内容互不相同的子串。
A. 12
B. 13
C. 14
D. 15
答案:B。
解析:
abcab的不相同子串有:
长为0的子串:空串
长为1的子串:a,b,c
长为2的子串:ab,bc,ca
长为3的子串:abc,bca,cab
长为4的子串:abca,bcab
长为5的子串:abcab
共有13个。
15. 以下对递归方法的描述中,正确的是:( )
A. 递归是允许使用多组参数调用函数的编程技术
B. 递归是通过调用自身来求解问题的编程技术
C. 递归是面向对象和数据而不是功能和逻辑的编程语言模型
D. 递归是将用某种高级语言转换为机器代码的编程技术
答案:B。
解析:
选项A:不清楚什么叫“多组参数调用函数”,任意一个带参函数,都可以用多组参数来调用。
选项B:论述正确。
选项C:面向对象编程(或者说“类”)是面向对象和数据而不是功能和逻辑的编程语言模型。
选项D:编译是将用某种高级语言转换为机器代码的编程技术
二、阅读程序
(程序输入不超过数组或字符串定义的范围;判断题正确填√,错误填×;除特殊说明外,判断题 1.5 分,选择题 3 分,共计 40 分)
(1) 第16-21题
#include <iostream>
using namespace std;
int main()
{
unsigned short x, y;
cin >> x >> y;
x = (x | x << 2) & 0x33;
x = (x | x << 1) & 0x55;
y = (y | y << 2) & 0x33;
y = (y | y << 1) & 0x55;
unsigned short z = x | y << 1;
cout << z << endl;
return 0;
}
判断题
16. 删去第 7 行与第 13 行的 unsigned ,程序行为不变。( )
17. 将第 7 行与第 13 行的 short 均改为 char ,程序行为不变。( )
18. 程序总是输出一个整数“ 0 ”。( )
19. 当输入为“ 2 2 ”时,输出为“ 10 ”。( )
20. 当输入为“ 2 2 ”时,输出为“ 59 ”。( )
单选题
21. 当输入为“ 13 8 ”时,输出为( )。
A. “ 0 ”
B. “ 209 ”
C. “ 197 ”
D. “ 226 ”
(2) 第22-27题
#include <algorithm>
#include <iostream>
#include <limits>
using namespace std;
const int MAXN = 105;
const int MAXK = 105;
int h[MAXN][MAXK];
int f(int n, int m)
{
if (m == 1) return n;
if (n == 0) return 0;
int ret = numeric_limits<int>::max();
for (int i = 1; i <= n; i++)
ret = min(ret, max(f(n - i, m), f(i - 1, m - 1)) + 1);
return ret;
}
int g(int n, int m)
{
for (int i = 1; i <= n; i++)
h[i][1] = i;
for (int j = 1; j <= m; j++)
h[0][j] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 2; j <= m; j++) {
h[i][j] = numeric_limits<int>::max();
for (int k = 1; k <= i; k++)
h[i][j] = min(
h[i][j],
max(h[i - k][j], h[k - 1][j - 1]) + 1);
}
}
return h[n][m];
}
int main()
{
int n, m;
cin >> n >> m;
cout << f(n, m) << endl << g(n, m) << endl;
return 0;
}
(3) 第28-34题
#include <iostream>
using namespace std;
int n, k;
int solve1()
{
int l = 0, r = n;
while (l <= r) {
int mid = (l + r) / 2;
if (mid * mid <= n) l = mid + 1;
else r = mid - 1;
}
return l - 1;
}
double solve2(double x)
{
if (x == 0) return x;
for (int i = 0; i < k; i++)
x = (x + n / x) / 2;
return x;
}
int main()
{
cin >> n >> k;
double ans = solve2(solve1());
cout << ans << ' ' << (ans * ans == n) << endl;
return 0;
}
三、完善程序
(1) 第35-39题
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> fac;
fac.reserve((int)ceil(sqrt(n)));
int i;
for (i = 1; i * i < n; ++i) {
if (①) {
fac.push_back(i);
}
}
for (int k = 0; k < fac.size(); ++k) {
cout << ② << " ";
}
if (③) {
cout << ④ << " ";
}
for (int k = fac.size() - 1; k >= 0; --k) {
cout << ⑤ << " ";
}
}
(2) 第40-44题
#include <bits/stdc++.h>
using namespace std;
const int ROWS = 8;
const int COLS = 8;
struct Point {
int r, c;
Point(int r, int c) : r(r), c(c) {}
};
bool is_valid(char image[ROWS][COLS], Point pt,
int prev_color, int new_color) {
int r = pt.r;
int c = pt.c;
return (0 <= r && r < ROWS && 0 <= c && c < COLS &&
① && image[r][c] != new_color);
}
void flood_fill(char image[ROWS][COLS], Point cur, int new_color) {
queue<Point> queue;
queue.push(cur);
int prev_color = image[cur.r][cur.c];
②;
while (!queue.empty()) {
Point pt = queue.front();
queue.pop();
Point points[4] = {③, Point(pt.r - 1, pt.c),
Point(pt.r, pt.c + 1), Point(pt.r, pt.c - 1)};
for (auto p : points) {
if (is_valid(image, p, prev_color, new_color)) {
④;
⑤;
}
}
}
}
int main() {
char image[ROWS][COLS] = {{'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g'},
{'g', 'g', 'g', 'g', 'g', 'g', 'r', 'r'},
{'g', 'r', 'r', 'g', 'g', 'r', 'g', 'g'},
{'g', 'b', 'b', 'b', 'b', 'r', 'g', 'r'},
{'g', 'g', 'g', 'b', 'b', 'r', 'g', 'r'},
{'g', 'g', 'g', 'b', 'b', 'b', 'b', 'r'},
{'g', 'g', 'g', 'g', 'g', 'b', 'g', 'g'},
{'g', 'g', 'g', 'g', 'g', 'b', 'b', 'g'}};
Point cur(4, 4);
char new_color = 'y';
flood_fill(image, cur, new_color);
for (int r = 0; r < ROWS; r++) {
for (int c = 0; c < COLS; c++) {
cout << image[r][c] << " ";
}
cout << endl;
}
// 输出:
// g g g g g g g g
// g g g g g g r r
// g r r g g r g g
// g y y y y r g r
// g g g y y r g r
// g g g y y y y r
// g g g g g y g g
// g g g g g y y g
return 0;
}