GESP 客观题评测系统
2025-12-Level-6
2025-12-Level-6
试卷解析总览,可直接查看每题答案与解析。
第 1 题(单选题)
在面向对象编程中,下列关于虚函数的描述中,错误的是()。
正确答案C
解析详情
【答案】C
【考点】虚函数;构造函数;运行时多态
【解析】 虚函数通过虚函数表实现动态绑定,用于支持多态。构造函数在执行时,对象的虚函数表指针尚未完全初始化,且构造函数不需要多态语义,因此 C++ 不允许构造函数为虚函数。析构函数设为虚函数是为了确保派生类对象能被正确释放。
【易错点】混淆构造函数与析构函数的虚函数性质。
第 2 题(单选题)
执行如下代码,会输出 钢琴:叮咚叮咚 和 吉他:咚咚当当 。这体现了面向对象编程的( )特性。
class Instrument {
public:
virtual void play() {
cout << "乐器在演奏声音" << endl;
}
virtual ~Instrument() {}
};
class Piano : public Instrument {
public:
void play() override {
cout << "钢琴:叮咚叮咚" << endl;
}
};
class Guitar : public Instrument {
public:
void play() override {
cout << "吉他:咚咚当当" << endl;
}
int main() {
Instrument* instruments[2];
instruments[0] = new Piano();
instruments[1] = new Guitar();
for (int i = 0; i < 2; ++i) {
instruments[i]->play();
}
for (int i = 0; i < 2; ++i) {
delete instruments[i];
}
return 0;
}
}正确答案C
解析详情
【答案】C
【考点】面向对象编程;运行时多态;虚函数
【解析】 代码中通过基类指针 Instrument* 调用虚函数 play()。在运行时,程序会根据指针实际指向的对象类型(Piano 或 Guitar)执行对应的成员函数。这种“一个接口,多种实现”的现象即为多态。
【易错点】容易将继承关系误认为本题体现的核心特性。
第 3 题(单选题)
关于以下代码,说法正确的是()。
class Instrument {
public:
void play() {
cout << "乐器在演奏声音" << endl;
}
virtual ~Instrument() {}
};
class Piano : public Instrument {
public:
void play() override {
cout << "钢琴:叮咚叮咚" << endl;
}
};
class Guitar : public Instrument {
public:
void play() override {
cout << "吉他:咚咚当当" << endl;
}
int main() {
Instrument* instruments[2];
instruments[0] = new Piano();
instruments[1] = new Guitar();
for (int i = 0; i < 2; ++i) {
instruments[i]->play();
}
for (int i = 0; i < 2; ++i) {
delete instruments[i];
}
return 0;
}
}正确答案C
解析详情
【答案】C
【考点】虚函数;override 关键字;编译检查
【解析】 在 C++ 中,override 关键字用于显式声明重写基类的虚函数。如果基类中对应的函数没有被声明为 virtual,编译器会报错。本题基类 play() 缺少 virtual 关键字,因此派生类的 override 声明会导致编译失败。
【易错点】忽略 override 对基类虚函数声明的强制依赖。
第 4 题(单选题)
某文本编辑器把用户输入的字符依次压入栈 S。用户依次输入 A,B,C,D 后,用户按了两次撤销(每次撤销,弹出栈顶一个字符)。此时栈从栈底到栈顶的内容是:()。
正确答案A
解析详情
【答案】A
【考点】栈;后进先出(LIFO);撤销操作
【解析】 栈遵循后进先出原则。依次入栈 A、B、C、D 后,栈顶为 D。撤销操作相当于弹出栈顶:第一次撤销弹出 D,第二次撤销弹出 C。最后栈内剩余元素从底到顶依次为 A、B。
【易错点】将撤销操作误解为从栈底删除。
第 5 题(单选题)
假设循环队列数组长度为 N,其中队空判断条件为:front == rear,队满判断条件为:(rear + 1) % N == front,出队对应的操作为:front = (front + 1) % N,入队对于的操作为:rear = (rear + 1) % N。循环队列长度 N = 6,初始 front = 1,rear = 1,执行操作序列为:入队,入队,入队,出队,入队,入队,则最终(front,rear)的值是()。
正确答案B
解析详情
【答案】B
【考点】循环队列;取模运算;指针更新
【解析】 初始 front=1, rear=1。操作序列:入队(1,2)、入队(1,3)、入队(1,4)、出队(2,4)、入队(2,5)、入队(2,0)(注:(5+1)%6=0)。最终状态为 (2, 0)。计算过程中需严格按照 (rear + 1) % N 更新指针。
【易错点】模运算边界处理,如 5+1 取模后应回到 0。
第 6 题(单选题)
以下函数 check() 用于判断一棵二叉树是否为()。
bool check(TreeNode* root) {
if (!root) return true;
queue<TreeNode*> q;
q.push(root);
bool hasNull = false;
while (!q.empty()) {
TreeNode* cur = q.front(); q.pop();
if (!cur) {
hasNull = true;
} else {
if (hasNull) return false;
q.push(cur->left);
q.push(cur->right);
}
}
return true;
}正确答案B
解析详情
【答案】B
【考点】二叉树分类;层序遍历;完全二叉树判定
【解析】 该函数使用队列进行层序遍历。hasNull 变量记录是否遇到了空结点。在完全二叉树中,空结点只能出现在序列末尾。如果遇到空结点后又出现了非空结点(hasNull 为真且 cur 不为空),则说明不是完全二叉树。
【易错点】分不清完全二叉树和满二叉树的层序特征。
第 7 题(单选题)
以下代码实现了二叉树的()。
void traverse(TreeNode* root) {
if (!root) return;
traverse(root->left);
traverse(root->right);
cout << root->val << " ";
}正确答案C
解析详情
【答案】C
【考点】二叉树遍历;递归;后序遍历
【解析】 递归函数的执行顺序是:先递归左子树,再递归右子树,最后访问根结点(输出 val)。这种“左-右-根”的访问顺序定义为后序遍历。
【易错点】将递归调用的顺序与访问结点的时机混淆。
第 8 题(单选题)
下面代码实现了哈夫曼编码,则横线处应填写的代码是()。
struct Symbol {
char ch; //字符
long long freq; //频率
string code; //哈夫曼编码
};
struct Node {
long long w; //权值
int l, r; //左右孩子(节点下标),-1 表示空
int sym; //叶子对应符号下标;内部节点为 -1
Node(long long _w=0, int _l=-1, int _r=-1, int _sym=-1)
: w(_w), l(_l), r(_r), sym(_sym) {}
};
// 从 A(leafIdx) 和 B(internalIdx) 的队首取最小的一个节点下标
static int PopMinNode(const vector<Node>& nodes,
const vector<int>& leafIdx, int n, int& pA,
const vector<int>& internalIdx, int& pB) {
if (pA < n && (pB >= (int)internalIdx.size() ||
nodes[leafIdx[pA]].w <= nodes[internalIdx[pB]].w)) {
return leafIdx[pA++];
}
else {
return internalIdx[pB++];
}
}
// DFS 生成编码(左 0,右 1)
static void DFSBuildCodes(int u, const vector<Node>& nodes, Symbol sym[], string& path) {
if (u == -1) return;
if (nodes[u].sym != -1) { //叶子
sym[nodes[u].sym].code = path;
return;
}
path.push_back('0');
DFSBuildCodes(nodes[u].l, nodes, sym, path);
path.pop_back();
path.push_back('1');
DFSBuildCodes(nodes[u].r, nodes, sym, path);
path.pop_back();
}
int BuildHuffmanCodes(Symbol sym[], int n) {
for (int i = 0; i < n; i++) sym[i].code.clear();
if (n <= 0) return -1;
// 只有一个字符:约定编码为 "0"
if (n == 1) {
sym[0].code = "0";
return 0;
}
vector<Node> nodes;
nodes.reserve(2 * n);
// 1) 建立叶子节点
vector<int> leafIdx(n);
for (int i = 0; i < n; i++) {
leafIdx[i] = (int)nodes.size();
nodes.push_back(Node(sym[i].freq, -1, -1, i));
}
// 2) 叶子按权值排序 (A 队列)
sort(leafIdx.begin(), leafIdx.end(),
[&](int a, int b) {
if (nodes[a].w != nodes[b].w) return nodes[a].w < nodes[b].w;
return nodes[a].sym < nodes[b].sym; // 稳定一下
});
// B 队列 (内部节点下标队列)
vector<int> internalIdx;
internalIdx.reserve(n);
int pA = 0, pB = 0;
// 3) 合并 n-1 次
for (int k = 1; k < n; k++) {
int x = PopMinNode(nodes, leafIdx, n, pA, internalIdx, pB);
int y = PopMinNode(nodes, leafIdx, n, pA, internalIdx, pB);
int z = (int)nodes.size();
_____________// 在此处填写代码
}
int root = internalIdx.back();
// 4) DFS 生成编码
string path;
DFSBuildCodes(root, nodes, sym, path);
return root;
}nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, -1));
internalIdx.push_back(z);nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, -1));
leafIdx.push_back(z);internalIdx.push_back(z);
nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, x + y));nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, x+y));
leafIdx.push_back(z);正确答案A
解析详情
【答案】A
【考点】哈夫曼树;贪心算法;内部结点构造
【解析】 构造哈夫曼树时,每次取两个权值最小的结点合并。新生成的内部结点权值为两者之和,其左右孩子指向选出的两结点,且内部结点不直接对应符号(sym = -1)。最后需将新结点下标 z 加入内部结点队列。
【易错点】误将内部结点下标加入叶子结点队列 leafIdx。
第 9 题(单选题)
以下关于哈夫曼编码的说法,正确的是()。
正确答案B
解析详情
【答案】B
【考点】哈夫曼编码;前缀码性质;变长编码
【解析】 哈夫曼编码是一种变长编码。其核心性质是前缀码,即任一字符的编码都不是另一个字符编码的前缀,从而保证解码的唯一性。哈夫曼编码是根据频率构造的,不一定唯一,且主要用于数据压缩。
【易错点】错误认为最优编码结果是唯一的。
第 10 题(单选题)
以下函数实现了二叉排序树(BST)的()操作。
TreeNode* op(TreeNode* root, int x) {
if (!root) return new TreeNode(x);
if (x < root->val)
root->left = op(root->left, x);
else
root->right = op(root->right, x);
return root;
}正确答案B
解析详情
【答案】B
【考点】二叉排序树(BST);递归算法;结点插入
【解析】 函数通过递归比较 x 与当前结点值的大小。如果 x 较小则去左子树,较大则去右子树。当遇到空指针时,新建结点并返回,这实现了将新值插入到 BST 合适位置的操作。
【易错点】将具有查找逻辑的插入操作误认为是单纯的查找。
第 11 题(单选题)
下列代码实现了树的深度优先遍历,则横线处应填入()。
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x): val(x), left(nullptr), right(nullptr) {}
};
void dfs(TreeNode* root) {
if (!root) return;
stack<TreeNode*> st;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); st.pop();
cout << node->val << " ";
if (node->right) st.push(node->right);
___________
}
}正确答案A
解析详情
【答案】A
【考点】深度优先搜索(DFS);栈;前序遍历实现
【解析】 使用栈模拟 DFS 遍历时,为了保证左子树先于右子树被处理,由于栈是后进先出,必须先压入右孩子,再压入左孩子。因此横线处应为压入左孩子的操作。
【易错点】在栈操作中错误地先压入左孩子。
第 12 题(单选题)
给定一棵普通二叉树(节点值没有大小规律),下面代码判断是否存在值为 x 的结点,则横线处应填入()。
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x): val(x), left(nullptr), right(nullptr) {}
};
TreeNode* bfsFind(TreeNode* root, int x) {
if (!root) return nullptr;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* cur = q.front(); q.pop();
if (cur->val == x) return cur;
___________
}
return nullptr;
}if (cur->left) q.push(cur->left);
if (cur->right) q.push(cur->right);q.push(cur->left);
q.push(cur->right);正确答案C
解析详情
【答案】C
【考点】广度优先搜索(BFS);队列;二叉树搜索
【解析】 BFS 查找的核心是使用队列层序扩展。从队列取出当前结点检查值后,若非目标值,则需将其存在的左、右孩子依次入队。只有这样才能遍历到树中所有结点。选项 C 包含了必要的空指针检查。
【易错点】漏掉子结点入队前的空指针判断。
第 13 题(单选题)
在二叉排序树(Binary Search Tree, BST)中,假设节点值互不相同。给定如下搜索函数,以下说法一定正确的是()。
bool find(Node* root, int x) {
while (root) {
if (root->val == x) return true;
root = (x < root->val) ? root->left : root->right;
}
return false;
}正确答案B
解析详情
【答案】B
【考点】二叉排序树(BST);最坏时间复杂度;退化树
【解析】 BST 的查找效率取决于树的高度。在最坏情况下,BST 可能退化为单支树(类似于链表),此时树高为 n,查找一个值需要比较 n 次,即时间复杂度为 O(n)。
【易错点】混淆平均时间复杂度 O(log n) 与最坏时间复杂度。
第 14 题(单选题)
0/1 背包(每件物品最多选一次)问题通常可用一维动态规划求解,核心代码如下。则下面说法正确的是()。
for each item (w, v):
for (int j = W; j >= w; --j)
dp[j] = max(dp[j], dp[j-w] + v);正确答案B
解析详情
【答案】B
【考点】0/1 背包问题;动态规划;一维空间优化
【解析】 在 0/1 背包的一维实现中,外层循环遍历物品,内层循环遍历容量。为了确保每件物品只被选一次,更新 dp[j] 时必须使用上一轮物品的状态,因此 j 必须从大到小逆序遍历。正序遍历会导致同一物品被多次选取。
【易错点】不理解容量循环方向与物品选取次数的关系。
第 15 题(单选题)
以下关于动态规划的说法中,错误的是()。
正确答案B
解析详情
【答案】B
【考点】动态规划;计算复杂度;状态转移
【解析】 动态规划的时间复杂度通常等于“状态总数 × 单个状态转移的复杂度”。如果每个状态的转移需要遍历(如区间 DP 或背包问题),总复杂度将高于状态个数。因此 B 选项中“复杂度通常为状态个数”的表述是错误的。
【易错点】忽略了状态转移过程中的额外枚举代价。
判断题(每题 2 分)
第 1 题(判断题)
以下代码中,构造函数被调用的次数是1次。
class Test {
public:
Test() { cout << "T"; }
};
int main() {
Test a;
Test b = a;
}
}正确答案错误
解析详情
【答案】错误
【考点】构造函数;拷贝构造函数;对象初始化
【解析】 Test a; 调用一次默认构造函数。Test b = a; 使用 a 初始化 b,调用一次拷贝构造函数。虽然代码未定义,编译器会提供默认实现。因此总共调用了两次构造相关函数,而非一次。
【易错点】忽略了拷贝构造函数也是构造过程的一部分。
第 2 题(判断题)
面向对象编程中,封装是指将数据和操作数据的方法绑定在一起,并对外隐藏实现细节。
正确答案正确
解析详情
【答案】正确
【考点】面向对象编程;封装;信息隐藏
【解析】 封装是面向对象的三大特性之一。它将属性(数据)和行为(方法)组合在一个类中,并通过访问修饰符限制外部直接访问内部实现,仅通过接口交互,从而提高安全性。
【易错点】仅理解为“代码归类”,而忽略了“对外隐藏”的关键点。
第 3 题(判断题)
以下代码能够正确统计二叉树中叶子结点的数量。
int countLeaf(TreeNode* root) {
if (!root) return 0;
if (!root->left && !root->right) return 1;
return countLeaf(root->left) + countLeaf(root->right);
}正确答案正确
解析详情
【答案】正确
【考点】二叉树算法;递归;叶子结点判定
【解析】 代码通过递归统计叶子结点:若为空则返回 0;若左右子树皆为空(符合叶子结点定义),则返回 1;否则递归累加左右子树的结果。逻辑严密,可以正确统计。
【易错点】在递归出口处对叶子结点判定条件的理解。
第 4 题(判断题)
广度优先遍历二叉树可用栈来实现。
正确答案错误
解析详情
【答案】错误
【考点】广度优先遍历(BFS);队列;数据结构应用
【解析】 广度优先遍历(BFS)是按层访问的,符合“先见到的结点先访问”的特性,应使用队列(先进先出)。栈(后进先出)通常用于实现深度优先遍历(DFS)。
【易错点】将 BFS 和 DFS 对应的数据结构混淆。
第 5 题(判断题)
函数调用管理可用栈来管理。
正确答案正确
解析详情
【答案】正确
【考点】函数调用机制;调用栈;系统内存管理
【解析】 在计算机系统中,函数调用过程中的参数传递、返回地址保存及局部变量分配都是通过“栈”这一数据结构管理的。这符合后进先出的调用逻辑,即最后调用的函数最先返回。
【易错点】将系统底层的调用栈与程序中的 std::stack 概念剥离。
第 6 题(判断题)
在二叉排序树(BST)中,若某结点的左子树为空,则该结点一定是整棵树中的最小值结点。
正确答案错误
解析详情
【答案】错误
【考点】二叉排序树(BST);最小值性质;树结构搜索
【解析】 BST 的最小值位于整棵树最左侧的结点。虽然某个结点左子树为空说明它是其子树中最小的,但如果该结点本身是某个祖先结点的右孩子,那么该祖先结点及其左子树中必然存在更小的值。
【易错点】将“子树内最小”等同于“全局最小”。
第 7 题(判断题)
下面的函数能正确判断一棵树是不是二叉排序树(左边的数字要比当前数字小,右边的数字要比当前数字大)。
bool isBST(TreeNode* root, int minVal, int maxVal) {
if (!root) return true;
if (root->val <= minVal || root->val >= maxVal)
return false;
return isBST(root->left, minVal, root->val) &&
isBST(root->right, root->val, maxVal);
}正确答案正确
解析详情
【答案】正确
【考点】二叉排序树(BST);合法性判定;范围约束
【解析】 判断 BST 不能仅看父子关系,必须保证左子树所有结点都小于根,右子树所有结点都大于根。该函数通过传递 minVal 和 maxVal 约束了整棵子树的取值范围,逻辑是正确的。
【易错点】忽视了 BST 定义中对全局路径范围的要求。
第 8 题(判断题)
格雷编码相邻两个编码之间必须有多位不同,以避免数据传输错误。
正确答案错误
解析详情
【答案】错误
【考点】格雷编码;编码特性;循环码
【解析】 格雷码(Gray Code)最核心的特性是相邻两个代码之间只有一位二进制数不同。这种特性可以有效避免多位突变引起的瞬时错误。题干描述“必须有多位不同”与事实相反。
【易错点】记反了格雷码“最小变化”的设计初衷。
第 9 题(判断题)
小杨在玩一个闯关游戏,从第1关走到第4关。每一关的体力消耗如下(下标表示关卡编号): cost = [0, 3, 5, 2, 4],其中 cost[i] 表示到达第 i 关需要消耗的体力, cost[0]=0 表示在开始状态,体力消耗为 0。小杨每次可以从当前关卡前进1步或2步。按照上述规则,从第1关到第4关所需消耗的最小体力为7。
正确答案错误
解析详情
【答案】错误
【考点】动态规划;最小路径代价;手动推演
【解析】 到达第 1 关已经在起点。从 1 关到 4 关:根据最优子结构,dp[1]=0, dp[2]=cost[2]=5, dp[3]=cost[3]+min(dp[1],dp[2])=2+0=2, dp[4]=cost[4]+min(dp[2],dp[3])=4+2=6。最小体力应为 6,非 7。
【易错点】对关卡索引和体力消耗计入时机的理解偏差。
第 10 题(判断题)
假定只有一个根节点的树的深度为 1,则一棵有 n 个节点的完全二叉树,则树的深度为。
正确答案正确
解析详情
【答案】正确
【考点】完全二叉树;树高计算;地板函数
【解析】 对于有 n 个结点的完全二叉树,如果根深度为 1,则深度 h 的公式为 floor(log2(n)) + 1。例如 n=1 时深度为 1,n=3 时深度为 2,均符合该公式。
【易错点】记混 floor 和 ceil 在树高公式中的使用条件。