GESP 客观题评测系统
2025-09-Level-6
2025-09-Level-6
试卷解析总览,可直接查看每题答案与解析。
第 1 题(单选题)
下列关于类的说法,错误的是()。
正确答案D
解析详情
【答案】D
【考点】虚析构函数与多态内存管理
【解析】 如果基类的析构函数不是虚函数,当通过基类指针删除一个派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类中特有的资源(如动态分配的内存)无法被释放,从而引起内存泄漏或未定义行为。因此,D 选项说法错误。
【易错点】 容易忽视非虚析构函数在多态删除时的资源泄露问题。
第 2 题(单选题)
假设变量 veh 是类 Car 的一个实例,我们可以调用 veh.move(),是因为面向对象编程有()性质。
class Vehicle {
private:
string brand;
public:
Vehicle(string b) : brand(b) {}
void setBrand(const string& b) { brand = b; }
string getBrand() const { return brand; }
void move() const {
cout << brand << " is moving..." << endl;
}
};
class Car : public Vehicle {
private:
int seatCount;
public:
Car(string b, int seats) : Vehicle(b), seatCount(seats) {}
void showInfo() const {
cout << "This car is a " << getBrand() << " with " << seatCount << " seats." << endl;
}
};正确答案A
解析详情
【答案】A
【考点】面向对象三大特性之继承
【解析】 代码中类 Car 通过 `public Vehicle` 继承自类 Vehicle。由于 move() 是 Vehicle 类中的公有成员函数,Car 类继承了该函数。因此,作为 Car 实例的 veh 可以直接调用基类中定义的 move() 方法。这体现了继承使派生类获得基类属性和行为的特性。
【易错点】 容易将继承与多态混淆,注意此题仅涉及子类调用父类已有方法。
第 3 题(单选题)
下面代码中 v1 和 v2 调用了相同接口 move(),但输出结果不同,这体现了面向对象编程的( )特性。
class Vehicle {
private:
string brand;
public:
Vehicle(string b) : brand(b) {}
void setBrand(const string& b) { brand = b; }
string getBrand() const { return brand; }
virtual void move() const {
cout << brand << " is moving..." << endl;
}
};
class Car : public Vehicle {
private:
int seatCount;
public:
Car(string b, int seats) : Vehicle(b), seatCount(seats) {}
void showInfo() const {
cout << "This car is a " << getBrand() << " with " << seatCount << " seats." << endl;
}
void move() const override {
cout << getBrand() << " car is driving on the road!" << endl;
}
};
class Bike : public Vehicle {
public:
Bike(string b) : Vehicle(b) {}
void move() const override {
cout << getBrand() << " bike is cycling on the path!" << endl;
}
};
int main() {
Vehicle* v1 = new Car("Toyota", 5);
Vehicle* v2 = new Bike("Giant");
v1->move();
v2->move();
delete v1;
delete v2;
return 0;
}正确答案C
解析详情
【答案】C
【考点】面向对象三大特性之多态
【解析】 代码中 v1 和 v2 都是基类 Vehicle 的指针,分别指向 Car 和 Bike 对象。基类中的 move() 被声明为 virtual 虚函数,子类对其进行了 override 重写。运行时通过基类指针调用该接口会根据对象的实际类型执行相应的重写版本,产生不同行为。这正是多态性的体现。
【易错点】 必须有虚函数(virtual)和重写(override)才能实现动态多态。
第 4 题(单选题)
栈的操作特点是()。
正确答案B
解析详情
【答案】B
【考点】栈(Stack)的数据结构定义
【解析】 栈是一种限制在一端进行插入和删除操作的线性表。其操作遵循“后进先出”(Last In First Out, LIFO)或“先进后出”(First In Last Out, FILO)的原则。最后进入栈的元素最先被弹出,而最先进入的元素被压在栈底,最后才能处理。
【易错点】 容易将栈(LIFO)与队列(FIFO)的操作特点记混。
第 5 题(单选题)
循环队列常用于实现数据缓冲。假设一个循环队列容量为 5(即最多存放 4 个元素,留一个位置区分空与满),依次进行操作:入队数据1,2,3,出队1个数据,再入队数据4和5,此时队首到队尾的元素顺序是()。
正确答案A
解析详情
【答案】A
【考点】循环队列的入队与出队操作
【解析】 模拟操作过程: 1. 入队 1, 2, 3:队列为 [1, 2, 3] 2. 出队 1 个数据:队首元素 1 出队,队列变为 [2, 3] 3. 再入队 4 和 5:4 和 5 依次进入队尾,队列变为 [2, 3, 4, 5] 此时队列中共有 4 个元素,满足最多存放 4 个的要求,顺序为 2, 3, 4, 5。
【易错点】 注意循环队列是先进先出,出队操作会移除最早进入的元素。
第 6 题(单选题)
以下函数 createTree() 构造的树是什么类型?
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
TreeNode* createTree() {
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
return root;
}正确答案B
解析详情
【答案】B
【考点】二叉树的分类与定义
【解析】 分析构造过程:根节点 1 有左右孩子 2 和 3;节点 2 有左右孩子 4 和 5;节点 3 没有孩子。该树高度为 3,且叶子节点 4 和 5 都在最后一层,并且从左到右依次填充。这符合完全二叉树的定义:除最后一层外每层都满,且最后一层节点集中在左侧。它不是满二叉树(节点 3 没孩子)。
【易错点】 满二叉树要求每一层都是满的,而完全二叉树只要求节点按顺序填充。
第 7 题(单选题)
已知二叉树的中序遍历是[D, B, E, A, F, C],先序遍历是[A, B, D, E, C, F]。请问该二叉树的后序遍历结果是()。
正确答案A
解析详情
【答案】A
【考点】二叉树遍历推导
【解析】 1. 先序首位 A 为根。中序中 A 划分为左 [D,B,E] 和右 [F,C]。 2. 左子树先序 [B,D,E],B 为根;中序 [D,B,E] 表明 D 在左 E 在右。左子树后序 [D,E,B]。 3. 右子树先序 [C,F],C 为根;中序 [F,C] 表明 F 在左。右子树后序 [F,C]。 4. 组合根节点 A:后序为 [左子树后序] + [右子树后序] + 根 = [D,E,B,F,C,A]。
【易错点】 注意后序遍历顺序是左右根,推导子树结构时需严格匹配先序和中序。
第 8 题(单选题)
完全二叉树可以用数组连续高效存储,如果节点从1开始编号,则对有两个孩子节点的节点i,()。
正确答案A
解析详情
【答案】A
【考点】完全二叉树的顺序存储规律
【解析】 在以数组形式存储的完全二叉树中,如果根节点编号为 1,则编号为 i 的节点,其左孩子节点的编号一定为 2i,其右孩子节点的编号一定为 2i + 1(前提是存在孩子节点且不超出范围)。这是顺序存储中计算父子关系的固定公式。
【易错点】 注意编号是从 1 开始还是从 0 开始。若从 0 开始,则左孩子为 2i+1,右孩子为 2i+2。
第 9 题(单选题)
设有字符集 {a, b, c, d, e, f},其出现频率分别为 {5, 9, 12, 13, 16, 45}。哈夫曼算法构造最优前缀编码,以下哪一组可能是对应的哈夫曼编码?(非叶子节点左边分支记作 0,右边分支记作 1,左右互换不影响正确性)。
正确答案B
解析详情
【答案】B
【考点】哈夫曼树构造与编码
【解析】 1. 5+9=14;12+13=25;14+16=30;25+30=55;55+45=100。 2. f 频率 45 最高,直接连在根节点一侧,编码长度必为 1(如 0)。 3. a,b 频率最低,合并层次最深,编码最长(4位)。 B 选项中 f 为 0,a,b 为 4 位编码,c,d 为 3 位,e 为 3 位,符合构建规律。
【易错点】 哈夫曼编码必须满足前缀码要求,且频率越高编码越短。
第 10 题(单选题)
下面代码生成格雷编码,则横线上应填写()。
vector<string> grayCode(int n) {
if (n == 0) return {"0"};
if (n == 1) return {"0", "1"};
vector<string> prev = grayCode(n-1);
vector<string> result;
for (string s : prev) {
result.push_back("0" + s);
}
for (___, ___) { // 在此处填写代码
result.push_back("1" + prev[i]);
}
return result;
}正确答案B
解析详情
【答案】B
【考点】格雷码生成算法(递归与镜像)
【解析】 格雷码的生成逻辑是:n 位格雷码由 n-1 位格雷码生成。前半部分是 n-1 位格雷码顺序排列并前缀 0;后半部分是 n-1 位格雷码“逆序”排列并前缀 1。为了实现逆序,第二个 for 循环需要从 `prev.size() - 1` 开始递减遍历到 0。因此应填入 `int i = prev.size()-1; i >= 0; i--`。
【易错点】 格雷码的镜像特性要求后半部分必须是前一个状态的倒序。
第 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;
___<TreeNode*> temp; // 在此处填写代码
temp.push(root);
while (!temp.empty()) {
TreeNode* node = temp.top();
temp.pop();
cout << node->val << " ";
if (node->right) temp.push(node->right);
if (node->left) temp.push(node->left);
}
}正确答案D
解析详情
【答案】D
【考点】深度优先搜索(DFS)的非递归实现
【解析】 深度优先搜索通常使用“栈”(stack)来实现非递归遍历。代码中使用了 `temp.top()` 和 `temp.pop()`,这是 `std::stack` 特有的成员函数。在处理完当前节点后,由于栈是后进先出,为了先访问左孩子,需要先将右孩子压栈,再将左孩子压栈。
【易错点】 注意 `top()` 方法是栈特有的,队列使用 `front()`,且队列实现的是广度优先搜索。
第 12 题(单选题)
令 n 是树的节点数目,下列代码实现了树的广度优先遍历,其时间复杂度是()。
void bfs(TreeNode* root) {
if (!root) return;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* node = q.front();
q.pop();
cout << node->val << "";
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}正确答案A
解析详情
【答案】A
【考点】广度优先搜索(BFS)的时间复杂度
【解析】 广度优先搜索(BFS)会访问且仅访问树中的每一个节点一次。对于每一个节点,入队和出队操作的时间复杂度都是 O(1)。由于共有 n 个节点,总的时间开销与节点数量成线性关系。因此,该算法的时间复杂度是 O(n)。
【易错点】 不要将搜索树的高度混淆为遍历整个树的时间复杂度。
第 13 题(单选题)
在二叉排序树(Binary Search Tree, BST)中查找元素 50 ,从根节点开始:若根值为 60 ,则下一步应去搜索:
正确答案A
解析详情
【答案】A
【考点】二叉排序树(BST)的查找性质
【解析】 根据二叉排序树的定义:对于任一节点,其左子树中所有节点的值均小于该节点的值,右子树中所有节点的值均大于该节点的值。我们要查找 50,而当前根节点值为 60。由于 50 < 60,目标元素只可能存在于当前节点的左子树中。因此下一步应搜索左子树。
【易错点】 务必记清楚“左小右大”的 BST 基本规则。
第 14 题(单选题)
删除二叉排序树中的节点时,如果节点有两个孩子,则横线处应填入(),其中 findMax 和 findMin 分别为寻找树的最大值和最小值的函数。
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x): val(x), left(nullptr), right(nullptr) {}
};
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root) return nullptr;
if (key < root->val) {
root->left = deleteNode(root->left, key);
}
else if (key > root->val) {
root->right = deleteNode(root->right, key);
}
else {
if (!root->left) return root->right;
if (!root->right) return root->left;
TreeNode* temp = ___; // 在此处填写代码
root->val = temp->val;
root->right = deleteNode(root->right, temp->val);
}
return root;
}正确答案C
解析详情
【答案】C
【考点】二叉排序树的节点删除算法
【解析】 删除有两个孩子的 BST 节点时,通常用其“中序后继”(右子树的最小值)或“中序前驱”(左子树的最大值)来替换该节点。代码中随后执行了 `root->right = deleteNode(root->right, temp->val)`,说明是去右子树删除替换节点,因此 temp 必须是右子树的最小节点,即 `findMin(root->right)`。
【易错点】 注意替换后的删除路径。若去右子树删,必须选最小值;若去左子树删,必须选最大值。
第 15 题(单选题)
给定 n 个物品和一个最大承重为 W 的背包,每个物品有一个重量 wt[i] 和价值 val[i],每个物品只能选择放或不放。目标是选择若干个物品放入背包,使得总价值最大,且总重量不超过 W,则横线上应填写()。
int knapsack(int W, vector<int>& wt, vector<int>& val, int n) {
vector<int> dp(W+1, 0);
for (int i = 0; i < n; ++i) {
for (int w = W; w >= wt[i]; --w) {
// 在此处填写代码
}
}
return dp[W];
}正确答案D
解析详情
【答案】D
【考点】0-1 背包问题的动态规划实现
【解析】 这是 0-1 背包的一维数组优化实现。状态转移方程为:`dp[w] = max(dp[w], dp[w - wt[i]] + val[i])`。其中 `dp[w]` 表示不放入第 i 个物品,`dp[w - wt[i]] + val[i]` 表示放入第 i 个物品。内层循环从 W 到 `wt[i]` 逆序是为了保证更新时使用的是上一轮物品的状态,符合 0-1 背包的每件物品仅用一次的要求。
【易错点】 注意不要选成完全背包的更新方式(顺序循环)或错误的 max 参数。
判断题(每题 2 分)
第 1 题(判断题)
当基类可能被多态使用,其析构函数应该声明为虚函数。
正确答案正确
解析详情
【答案】正确
【考点】虚析构函数的作用
【解析】 为了确保在通过基类指针销毁派生类对象时,能够正确触发派生类的析构函数以清理子类特有资源,防止内存泄漏,基类的析构函数必须声明为 virtual。这是多态设计的基本准则。
【易错点】 若不加虚析构,程序可能不会报错但会发生内存泄露。
第 2 题(判断题)
哈夫曼编码是最优前缀码,且编码结果唯一。
正确答案错误
解析详情
【答案】错误
【考点】哈夫曼编码的性质
【解析】 哈夫曼编码确实是最优前缀码,但其结果并不唯一。在构造哈夫曼树的过程中,若遇到频率相等的节点,左右子树的选择顺序以及相同频率节点的选取顺序不同,都会导致生成的编码集不同(尽管平均带权路径长度 WPL 都是最优的)。
【易错点】 注意区分“WPL 最优”和“编码唯一性”。
第 3 题(判断题)
一个含有100个节点的完全二叉树,高度为8。
正确答案错误
解析详情
【答案】错误
【考点】二叉树节点数与层数的关系
【解析】 含有 n 个节点的完全二叉树的高度为 \lfloor\log_2 n\rfloor + 1。对于 100 个节点,\lfloor\log_2 100\rfloor = 6(因为 2^6=64, 2^7=128),所以高度应为 7 层,而不是 8 层。
【易错点】 层数计算公式记混或 log 底数计算不熟练。
第 4 题(判断题)
在 C++ STL 中,栈(std::stack)的 pop 操作返回栈顶元素并移除它。
正确答案错误
解析详情
【答案】错误
【考点】C++ STL stack 的接口定义
【解析】 在 C++ 的 `std::stack` 中,`pop()` 函数的返回类型是 `void`,它只负责移除栈顶元素,并不返回该值。要获取栈顶元素,必须先调用 `top()` 函数。
【易错点】 部分其他编程语言(如 Java/Python)的 pop 操作会返回值,容易与 C++ 搞混。
第 5 题(判断题)
循环队列通过模运算循环使用空间。
正确答案正确
解析详情
【答案】正确
【考点】循环队列的实现原理
【解析】 循环队列在逻辑上将数组视为首尾相连。在入队和出队移动头尾指针(front/rear)时,使用 `(ptr + 1) % size` 进行计算。通过这种模运算,当指针到达数组末尾时能自动回到下标 0,从而实现空间的循环利用。
【易错点】 理解模运算如何解决线性队列的“假溢出”问题。
第 6 题(判断题)
一棵有n个节点的二叉树一定有n-1条边。
正确答案正确
解析详情
【答案】正确
【考点】树的图论性质
【解析】 在树(包括二叉树)这种结构中,除了根节点外,每个节点都有且仅有一个入边(指向它的父节点)。因此,边数等于“有入边的节点总数”。由于共有 n 个节点,除了 1 个根节点外,其余 n-1 个节点都有入边,所以总边数恰好为 n-1。
【易错点】 常识性结论,容易纠结特殊形状的树,但该性质对所有树都成立。
第 7 题(判断题)
以下代码实现了二叉树的中序遍历。输入以下二叉树,中序遍历结果是 4 2 5 1 3 6 。
// 1
// / \
// 2 3
// / \ \
// 4 5 6struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
void inorderIterative(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* curr = root;
while (curr || !st.empty()) {
while (curr) {
st.push(curr);
curr = curr->left;
}
curr = st.top(); st.pop();
cout << curr->val << " ";
curr = curr->right;
}正确答案正确
解析详情
【答案】正确
【考点】二叉树中序遍历推导
【解析】 1. 中序遍历顺序为左-根-右。 2. 根节点 1 的左子树是 (4-2-5),右子树是 (3-6)。 3. 左子树内部中序:4(左) -> 2(根) -> 5(右)。 4. 右子树内部中序:3(根) -> 6(右)。 5. 全局合并:(4 2 5) -> 1 -> (3 6) = 4 2 5 1 3 6。代码实现逻辑也是标准的中序非递归写法。
【易错点】 注意图中 6 是 3 的右孩子,中序遍历中根在右孩子之前。
第 8 题(判断题)
下面代码实现的二叉排序树的查找操作时间复杂度是,其中 h 为树高。
TreeNode* searchBST(TreeNode* root, int val) {
while (root && root->val != val) {
root = (val < root->val) ? root->left : root->right;
}
return root;
}正确答案正确
解析详情
【答案】正确
【考点】BST 查找的时间复杂度
【解析】 在二叉排序树中查找时,每一步都会向下移动到左子节点或右子节点。由于搜索路径是一条从根节点向下的单向路径,其最大长度取决于树的高度 h。在最坏情况下,搜索会到达叶子节点或空路径,因此操作次数与树高成正比,复杂度为 O(h)。
【易错点】 只有在平衡二叉树时复杂度才为 O(log n),一般 BST 退化时可达 O(n),即 O(h) 是最准确描述。
第 9 题(判断题)
下面代码实现了动态规划版本的斐波那契数列计算,其时间复杂度是。
int fib_dp(int n) {
if (n <= 1) return n;
vector<int> dp(n+1);
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}正确答案错误
解析详情
【答案】错误
【考点】动态规划算法的时间复杂度
【解析】 代码中使用了一个长度为 n+1 的数组 dp,并通过一个从 2 到 n 的单层循环来填表计算每个状态。每个状态的计算只需常量时间的加法。因此,总的时间复杂度是 O(n),而非指数级的 O(2^n)(指数级通常对应朴素递归算法)。
【易错点】 混淆了朴素递归(指数级)与动态规划/迭代(线性级)的效率区别。
第 10 题(判断题)
有一排香蕉,每个香蕉有不同的甜度值。小猴子想吃香蕉,但不能吃相邻的香蕉。以下代码能找到小猴子吃到最甜的香蕉组合。
void findSelectedBananas(vector<int>& bananas, vector<int>& dp) {
vector<int> selected;
int i = bananas.size() - 1;
while (i >= 0) {
if (i == 0) {
selected.push_back(0);
break;
}
if (dp[i] == dp[i-1]) {
i--;
} else {
selected.push_back(i);
i -= 2;
}
}
reverse(selected.begin(), selected.end());
cout << "小猴子吃了第:";
for (int idx : selected) {
cout << idx + 1 << "";
cout << "个香蕉" << endl;
}
}
int main() {
vector<int> bananas = {1, 2, 3, 1}; // 每个香蕉的甜
vector<int> dp(bananas.size());
dp[0] = bananas[0];
dp[1] = max(bananas[0], bananas[1]);
for (int i = 2; i < bananas.size(); i++) {
dp[i] = max(bananas[i] + dp[i-2], dp[i-1]);
}
findSelectedBananas(bananas, dp);
return 0;
}正确答案正确
解析详情
【答案】正确
【考点】动态规划路径回溯
【解析】 主函数实现了经典的“打家劫舍”动态规划,`dp[i]` 存储到第 i 个香蕉时的最大甜度。`findSelectedBananas` 函数通过比较 `dp[i]` 和 `dp[i-1]` 来回溯判断第 i 个香蕉是否被选中:若 `dp[i] > dp[i-1]`,说明选中了第 i 个并向前跨 2 步;否则未选并前移 1 步。逻辑完全正确。
【易错点】 注意回溯时的判断逻辑:只有当当前最大值是由包含当前香蕉贡献时(即 dp[i] 不等于 dp[i-1]),才认定选中了该香蕉。