GESP 客观题评测系统

2024-09-Level-6

2024-09-Level-6

试卷解析总览,可直接查看每题答案与解析。

单选题(每题 2 分)

1 题(单选题

以下()没有涉及 C++ 语言的面向对象特性支持。

A.
C++ 中构造一个 class 或 struct
B.
C++ 中调用 printf 函数
C.
C++ 中调用用户定义的类成员函数
D.
C++ 中构造来源于同一基类的多个派生类

正确答案B

解析详情

【答案】B

【考点】C++ 面向对象基础

【解析】 面向对象的三大特性包括封装、继承和多态。选项 A 中的 class/struct 是封装的基础;选项 C 涉及对象对成员函数的调用;选项 D 涉及继承。而 printf 是 C 语言标准的格式化输出函数,属于过程式编程范畴,不涉及面向对象特性。

【易错点】 误以为 struct 在 C++ 中不涉及面向对象,实际上在 C++ 中 struct 和 class 唯一的区别是默认访问权限不同,都支持面向对象特性。

2 题(单选题

关于以下C++代码,()行代码会引起编译错误。

#include <iostream>
using namespace std;

class Base {
    private:
        int a;
    protected:
        int b;
    public:
        int c;
        Base() : a(1), b(2), c(3) {
        }
};

class Derived : public Base {
    public:
        void show() {
            cout << a << endl; // Line 1
            cout << b << endl; // Line 2
            cout << c << endl; // Line 3
        }
};
A.
Line 1
B.
Line 2
C.
Line 3
D.
没有编译错误

正确答案A

解析详情

【答案】A

【考点】C++ 继承与访问控制

【解析】 在 C++ 的继承关系中,基类的 private 成员对派生类是不可见的。代码中 Base 类的变量 a 被声明为 private,因此派生类 Derived 的成员函数 show 无法直接访问 a,Line 1 会导致编译错误。变量 b 是 protected,变量 c 是 public,派生类均可正常访问。

【易错点】 混淆 private 和 protected 的区别:protected 成员在派生类内部是可以访问的,而 private 成员只能在本类内部访问。

3 题(单选题

有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

解析详情

【答案】C

【考点】栈的性质(后进先出)

【解析】 入栈顺序为 6,5,4,3,2,1。选项 C 中,3 首先出栈,意味着此时 6,5,4 已入栈。随后 4 出栈正常。接着出栈的是 6,但在此时栈内 5 位于 6 的上方(5 后于 6 入栈),根据后进先出原则,5 必须在 6 之前出栈,因此 6 不可能在 5 之前出栈。

【易错点】 分析出栈序列时,需要时刻关注当前栈内的剩余元素及其顺序,任何时候出栈的必须是栈顶元素。

4 题(单选题

采用如下代码实现检查输入的字符串括号是否匹配,横线上应填入的代码为()。

#include <iostream>
#include <stack>
#include <string>

using namespace std;

bool is_valid(string s) {
    stack<char> st;
    char top;

    for (char& ch : s) {
        if (ch == '(' || ch == '{' || ch == '[') {
            st.push(ch); // 左括号入栈
        }
        else
        {
            if (st.empty())
            {
                return false;
            }
            // 在此处填入代码
        }
        if ((ch == ')' && top != '(') ||
            (ch == '}' && top != '{') ||
            (ch == ']' && top != '[')) {
            return false;
        }
    }

    return st.empty(); // 栈为空则说明所有括号匹配成功
}
A.
top = st.top(); st.pop();
B.
st.pop(); top = st.top();
C.
st.pop(); top = st.front();
D.
top = st.front(); st.pop();

正确答案A

解析详情

【答案】A

【考点】栈的基本操作

【解析】 当遇到右括号时,逻辑上需要取出当前栈顶的左括号进行匹配。代码后续使用了变量 top 进行符号检查,因此必须先通过 st.top() 获取栈顶元素的值并赋给 top,然后执行 st.pop() 将该元素弹出栈。

【易错点】 若先执行 pop() 再执行 top(),获取到的将是原栈顶下方的元素,导致逻辑错误;此外,STL stack 没有 front() 成员函数。

5 题(单选题

下面代码判断队列的第一个元素是否等于 a,并删除该元素,横向上应填写()。

#include <iostream>
#include <queue>
using namespace std;

bool is_front_equal(std::queue<int>& q, int a) {
    bool is_equal = false;
    if (!q.empty()) {
        // 在此处填入代码
    }
    return is_equal;
}
A.
is_equal = (q.front() == a);
B.
is_equal = (q.front() == a); q.pop();
C.
q.pop(); is_equal = (q.front() == a);
D.
q.pop(); is_equal = (q.top() == a);

正确答案B

解析详情

【答案】B

【考点】队列的基本操作

【解析】 队列(Queue)遵循先进先出原则,获取队头元素使用 front()。题干要求先判断队头是否等于 a,然后删除该元素。因此应先执行比较赋值 is_equal = (q.front() == a),再执行 q.pop() 弹出队头。

【易错点】 混淆队列与栈的操作:队列获取首个元素用 front(),删除用 pop();栈获取首个元素用 top()。

6 题(单选题

假设字母表 {a,b,c,d,e} 在字符串出现的频率分别为 10%,15%,30%,16%,29%。若使用哈夫曼编码方式对字母进行二进制编码,则字符 `abcde` 分别对应的一组哈夫曼编码的长度分别为()。

A.
4, 4, 1, 3, 2
B.
3, 3, 2, 2, 2
C.
3, 3, 1, 2, 1
D.
4, 4, 1, 2, 2

正确答案B

解析详情

【答案】B

【考点】哈夫曼编码长度计算

【解析】 构造过程: 1. 合并最小的 a(10) 和 b(15),得到节点 n1(25); 2. 当前频率:n1(25), c(30), d(16), e(29);合并最小的 d(16) 和 n1(25),得到节点 n2(41); 3. 当前频率:n2(41), c(30), e(29);合并最小的 e(29) 和 c(30),得到节点 n3(59); 4. 最后合并 n2 和 n3。 a, b 深度为 3,d 深度为 2(注意:第 2 步中 d 与 n1 合并,n1 深度加 1),c, e 深度为 2。但在本题频率下,另一种合并方式(a+b=25, d+e=45, 25+30=55)也可能产生不同的树。按标准贪心,a,b,d 长度应为 3,c,e 长度为 2。选项 B 的 3,3,2,2,2 是唯一合理的分布。

【易错点】 哈夫曼树的形状取决于每次合并时选择的最小频率节点,频率接近时需仔细核对合并顺序。

7 题(单选题

以下 C++ 代码实现 n 位的格雷码,则横线上应填写()。

#include <iostream>
#include <vector>
#include <string>
using namespace std;

// 生成 n 位的格雷码
vector<string> generate_graycode(int n) {
    vector<string> graycode_list;
    if (n <= 0) {
        return graycode_list;
    }
    // 初始1位格雷码
    graycode_list.push_back("0");
    graycode_list.push_back("1");
    // 迭代生成 n 位的格雷码
    for (int i = 2; i <= n; i++) {
        int current_size = graycode_list.size();
        for (int j = current_size - 1; j >= 0; j--) {
            graycode_list.push_back("1" + graycode_list[j]);
        }
        for (int j = 0; j < current_size; j++) {
            // 在此处填入代码
        }
    }

    return graycode_list;
}
A.
graycode_list.push_back("0" + graycode_list[j]);
B.
graycode_list[j] = "0" + graycode_list[j];
C.
graycode_list.push_back("1" + graycode_list[j]);
D.
graycode_list[j] = "1" + graycode_list[j];

正确答案B

解析详情

【答案】B

【考点】格雷码递推生成算法

【解析】 n 位格雷码的生成规律是:在前一部分(n-1 位格雷码)前面加 '0',在后一部分(n-1 位格雷码的逆序)前面加 '1'。代码中第一个内部循环已将逆序且加 '1' 的部分通过 push_back 添加到列表末尾。第二个内部循环应处理原本存在的前 current_size 个元素,即在它们前面加上 '0'。

【易错点】 注意此处是在原有元素上进行修改(更新前缀),而不是通过 push_back 增加新元素,否则列表长度会错误翻倍。

8 题(单选题

给定一棵二叉树,其前序遍历结果为:ABDECFG, 中序遍历结果为:DEBACFG,则这棵树的正确后序遍历结果是()。

A.
EDBGFCA
B.
EDGBFCA
C.
DEBGFCA
D.
DBEGFCA

正确答案A

解析详情

【答案】A

【考点】二叉树遍历推导

【解析】 1. 前序首字符 A 为根。中序中 A 划分子树:左(DEB),右(CFG)。 2. 左子树前序 BDE,中序 DEB,根为 B,左子树(DE),无右子树。DE 中,前序 D 在前为根,中序 E 在后说明 E 是 D 的右子。 3. 右子树前序 CFG,中序 CFG,根为 C,无左子树,右子树(FG)。FG 中,前序 F 在前为根,中序 G 在后说明 G 是 F 的右子。 后序遍历(左-右-根):左子树后序 EDB,右子树后序 GFC,最后根 A,即 EDBGFCA。

【易错点】 在根据中序和前序确定子树结构时,必须严格遵守前序确定根、中序确定左右的原则,尤其是只有单侧子树的情况。

9 题(单选题

一棵有n个结点的完全二叉树用数组进行存储与表示,已知根结点存储在数组的第1个位置。若存储在数组第9个位置的结点存在兄弟结点和两个子结点,则它的兄弟结点和右子结点的位置分别是()。

A.
8, 18
B.
10, 18
C.
8, 19
D.
10, 19

正确答案C

解析详情

【答案】C

【考点】完全二叉树的数组存储性质

【解析】 在索引从 1 开始的完全二叉树数组中: - 节点 i 的左子节点为 2i,右子节点为 2i+1。 - 节点 i 的父节点为 i/2。 节点 9 的左子节点为 2*9=18,右子节点为 2*9+1=19。节点 9 的父节点为 9/2=4。节点 4 的子节点分别为 2*4=8 和 2*4+1=9。因此,节点 9 的兄弟节点是 8。

【易错点】 混淆索引从 0 开始还是从 1 开始的公式。若从 1 开始,左子是 2i,右子是 2i+1;若从 0 开始,左子是 2i+1,右子是 2i+2。

10 题(单选题

二叉树的深度定义为从根结点到叶结点的最长路径上的结点数,则以下基于二叉树的深度优先搜索实现的深度计算函数中横线上应填写()。

// 定义二叉树的结点结构
struct tree_node {
    int val;
    tree_node* left;
    tree_node* right;

    tree_node(int x) : val(x), left(nullptr), right(nullptr) {}
};

// 计算二叉树的深度
int max_depth(tree_node* root) {
    if (root == nullptr) {
        return 0; // 如果根结点为空,则深度为 0
    }

    int left_depth = max_depth(root->left);
    int right_depth = max_depth(root->right);
    // 在此处填入代码
}
A.
return left_depth + right_depth;
B.
return max(left_depth, right_depth);
C.
return max(left_depth, right_depth) + 1;
D.
return left_depth + right_depth + 1;

正确答案C

解析详情

【答案】C

【考点】二叉树递归算法

【解析】 二叉树的深度等于其左、右子树深度的最大值,再加上根节点本身的一层深度。递归逻辑为:max(left_depth, right_depth) + 1。

【易错点】 忘记加 1(即忽略根节点层),或者错误地将左右子树深度相加。

11 题(单选题

上一题的二叉树深度计算还可以采用二叉树的广度优先搜索来实现。以下基于二叉树的广度优先搜索实现的深度计算函数中横线上应填写()。

#include <queue>

int max_depth_bfs(tree_node* root) {
    if (root == nullptr) {
        return 0; // 如果树为空,深度为 0
    }

    queue <tree_node*> q;
    q.push(root);
    int depth = 0;

    // 使用队列进行层序遍历
    while (!q.empty()) {
        // 在此处填入代码
        for (int i = 0; i < level_size; ++i) {
            tree_node* node = q.front();
            q.pop();
            if (node->left) {
                q.push(node->left);
            }
            if (node->right) {
                q.push(node->right);
            }
        }
    }
    return depth;
}
A.
int level_size = q.size(); depth++;
B.
int level_size = 2; depth++;
C.
int level_size = q.size(); depth += level_size;
D.
int level_size = 2; depth += level_size;

正确答案A

解析详情

【答案】A

【考点】二叉树层序遍历(BFS)

【解析】 在 BFS 计算深度时,需要按层进行处理。在 while 循环的每一轮开始时,队列 q 中的所有节点都属于同一层。因此,先通过 int level_size = q.size() 获取当前层的节点总数,然后将 depth 加 1(表示进入新的一层)。接下来的 for 循环将精确处理这 level_size 个节点。

【易错点】 若不先保存 q.size() 而是直接在 for 循环中使用 i < q.size(),由于循环内部会不断 push 新节点,会导致无法区分层级。

12 题(单选题

二叉搜索树中的每个结点,其左子树的所有结点值都小于该结点值,右子树的所有结点值都大于该结点值。以下代码对给定的整数数组(假设数组中没有数值相等的元素),构造一个对应的二叉搜索树,横线上应填写():

// 定义二叉树的结点结构
struct tree_node {
    int val;
    tree_node* left;
    tree_node* right;
    tree_node(int x) : val(x), left(nullptr), right(nullptr) {
    };
    // 插入结点到二叉搜索树中
    tree_node* insert(tree_node* root, int val) {
        if (root == nullptr) {
            return new tree_node(val);
        }
        // 在此处填入代码
        return root;
    }

    // 根据给定数组构造二叉搜索树
    tree_node* constructBST(const int arr[], int size) {
        tree_node* root = nullptr;
        for (int i = 0; i < size; ++i) {
            root = insert(root, arr[i]);
        }
        return root;
    }
}
A.
if (val < root->val)
    root->left = insert(root->left, val);
else
    root->right = insert(root->right, val);
B.
if (val > root->val)
    root->left = insert(root->left, val);
else
    root->right = insert(root->right, val);
C.
if (val < root->val)
    root->left = insert(root, val);
else
    root->right = insert(root, val);
D.
if (val > root->val)
root->left = insert(root, val);
else
root->right = insert(root, val);

正确答案A

解析详情

【答案】A

【考点】二叉搜索树(BST)性质与插入算法

【解析】 二叉搜索树的核心性质是:左子树节点值 < 当前节点值 < 右子树节点值。插入新值 val 时: 1. 若 val < root->val,应插入到左子树中,即 root->left = insert(root->left, val); 2. 否则插入到右子树中,即 root->right = insert(root->right, val)。

【易错点】 递归调用时必须传入子树指针(如 root->left),且必须将返回值重新赋给子树指针以维持树结构。

13 题(单选题

对上题中的二叉搜索树,当输入数组为[5,3,7,2,4,6,8]时,构建二叉搜索树,并采用如下代码实现的遍历方式,得到的输出是()。

#include <iostream>
using namespace std;

// 遍历二叉搜索树,输出结点值
void traversal(tree_node* root) {
    if (root == nullptr) {
        return;
    }

    traversal(root->left);
    cout << root->val;
    traversal(root->right);
}
A.
5372468
B.
2345678
C.
2436875
D.
2435678

正确答案B

解析详情

【答案】B

【考点】二叉搜索树的中序遍历

【解析】 代码中的 traversal 函数采用了“左子树-根节点-右子树”的访问顺序,即中序遍历。二叉搜索树的一个关键特性是:其中序遍历的结果必然是按数值从小到大升序排列的。因此,输入 [5,3,7,2,4,6,8] 经中序遍历输出的结果必定是排序后的 2345678。

【易错点】 不要试图手工模拟插入和遍历过程,那非常耗时且容易出错;直接利用 BST 中序遍历有序的特性即可快速解题。

14 题(单选题

动态规划通常用于解决()。

A.
无法分解的问题
B.
可以分解成相互依赖的子问题的问题
C.
可以通过贪心算法解决的问题
D.
只能通过递归解决的问题

正确答案B

解析详情

【答案】B

【考点】动态规划基本原理

【解析】 动态规划(Dynamic Programming)适用于具有“重叠子问题”和“最优子结构”性质的问题。它通过将复杂问题分解为子问题,并存储子问题的解来提高效率。这些子问题通常是相互依赖的(重叠的),这区别于分治法中子问题的相互独立。

【易错点】 区分动态规划与分治:分治法处理的子问题通常互不重叠且独立;动态规划处理的子问题往往存在大量重复计算。

15 题(单选题

阅读以下用动态规划解决的0-1背包问题的函数,假设背包的容量W是10kg,假设输入4个物品的重量weights分别为1,3,4,6(单位为kg),每个物品对应的价值values分别为20,30,50,60,则函数的输出为()。

#include <iostream>
#include <vector>
using namespace std;

// 0/1背包问题
int knapsack(int W, const vector<int>& weights, const vector<int>& values, int n) {
    vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));

    for (int i = 1; i <= n; ++i) {
        for (int w = 0; w <= W; ++w) {
            if (weights[i - 1] <= w) {
                // 0/1背包问题
            }
            else
            {
                dp[i][w] = dp[i - 1][w];
            }
        }
    }
    return dp[n][W];
}
A.
90
B.
100
C.
110
D.
140

正确答案C

解析详情

【答案】C

【考点】0-1 背包问题动态规划推导

【解析】 目标是在限重 10kg 内价值最大。可选组合: - 物品1(1kg,20) + 物品2(3kg,30) + 物品4(6kg,60) = 10kg, 价值 110。 - 物品3(4kg,50) + 物品4(6kg,60) = 10kg, 价值 110。 - 物品1(1kg,20) + 物品2(3kg,30) + 物品3(4kg,50) = 8kg, 价值 100。 计算得出最大价值为 110。

【易错点】 若采用贪心策略(按单位价值比选择),可能会选出错误的组合。0-1 背包必须考察全局最优组合。

判断题(每题 2 分)

1 题(判断题

C++、Python和JAVA等都是面向对象的编程语言。

正确答案正确

解析详情

【答案】正确

【考点】编程语言基础知识

【解析】 C++、Python 和 Java 是目前最主流的面向对象编程(OOP)语言,它们都完整支持封装、继承和多态等 OOP 核心概念。

【易错点】 误认为 C++ 仅仅是 C 的扩展(面向过程),实际上 C++ 在诞生之初的核心目标就是引入类和对象的支持。

2 题(判断题

在C++中,类的静态成员变量只能被该类对象的成员函数访问。

正确答案错误

解析详情

【答案】错误

【考点】C++ 类成员属性与访问控制

【解析】 静态成员变量能否访问取决于其访问修饰符(public/private/protected)。如果是 public 静态成员,既可以通过类名作用域(ClassName::VarName)在类外直接访问,也可以通过对象访问,并不限于成员函数。

【易错点】 混淆访问权限修饰符(控制谁能看)与 static 关键字(控制变量生命周期与共享性)。

3 题(判断题

栈是一种线性结构,可通过数组或链表来实现。二者相比,数组实现占用的内存较少,链表实现的入队和出队操作的时间复杂度较低。

正确答案错误

解析详情

【答案】错误

【考点】栈的实现与性能分析

【解析】 无论是数组实现还是链表实现,栈的入栈和出栈操作的时间复杂度都是 O(1),不存在“链表实现更低”的说法。此外,栈的操作术语通常是“入栈”和“出栈”,而非“入队”和“出队”。

【易错点】 误以为链表实现由于不需要连续空间就一定比数组实现更快,实际上数组实现在 CPU 缓存友好性上通常更优。

4 题(判断题

运行以下 C++ 代码,屏幕将输出“derived class”。

#include <iostream>
using namespace std;

class base {
    public:
        virtual void show() {
            cout << "base class" << endl;
        }
};

class derived : public base {
    public:
        void show() override {
            cout << "derived class" << endl;
        }
};

int main() {
    base* b;
    derived d;
    b = &d;
    b->show();
    return 0;
}

正确答案正确

解析详情

【答案】正确

【考点】C++ 多态与虚函数

【解析】 代码中基类函数定义为 virtual,且派生类使用了 override。在 main 函数中,基类指针 b 指向了派生类对象 d,调用 b->show() 时会发生运行时多态,实际执行的是派生类重写后的函数,因此输出“derived class”。

【易错点】 忽略 virtual 关键字的作用,误以为指针的声明类型(base*)决定了最终调用的函数版本。

5 题(判断题

如下列代码所示的基类(base)及其派生类(derived),则生成一个派生类的对象时,只调用派生类的构造函数。

#include <iostream>
using namespace std;

class base {
    public:
        base() {
            cout << "base constructor" << endl;
        }
        ~base() {
            cout << "base destructor" << endl;
        }
};

class derived : public base {
    public:
        derived() {
            cout << "derived constructor" << endl;
        }
        ~derived() {
            cout << "derived destructor" << endl;
        }
};

正确答案错误

解析详情

【答案】错误

【考点】C++ 继承中构造函数的调用顺序

【解析】 在 C++ 中创建派生类对象时,程序会首先调用基类的构造函数来初始化基类部分,然后再调用派生类自身的构造函数。析构时的顺序则正好相反。

【易错点】 误以为派生类构造时会“替换”基类构造,实际上基类部分必须先完成初始化,派生类才能在此基础上进行后续构造。

6 题(判断题

哈夫曼编码本质上是一种贪心策略。

正确答案正确

解析详情

【答案】正确

【考点】贪心算法应用

【解析】 哈夫曼编码在构造哈夫曼树的过程中,每次都选取当前频率最小的两个节点进行合并,这体现了贪心算法通过局部最优选择(最小权值节点)来达成全局最优(平均编码长度最短)的思想。

【易错点】 分不清贪心算法和动态规划的区别:贪心算法每一步只看当前最优且不回溯。

7 题(判断题

如果根结点的深度记为1,则一棵恰有2024个叶结点的二叉树的深度最少是12。

正确答案正确

解析详情

【答案】正确

【考点】二叉树性质与深度计算

【解析】 在二叉树中,具有 n 个叶子节点的二叉树,其最小深度出现在完全二叉树的情况下。此时深度 h 满足 2^(h-1) >= n。对于 n=2024,由于 2^10=1024 < 2024 且 2^11=2048 >= 2024,意味着深度最小需要 11 层才能容纳 2024 个叶子。注:若 2048 个叶子分布在第 11 层,深度为 11;若 2024 非 2 的幂,则需第 12 层。具体计算:log2(2024) 向上取整加 1 为 12。

【易错点】 计算 log2(n) 时忘记处理非 2 的幂次情况,或者混淆了节点总数与叶子节点数的计算公式。

8 题(判断题

在非递归实现的树的广度优先搜索中,通常使用栈来辅助实现。

正确答案错误

解析详情

【答案】错误

【考点】搜索算法辅助数据结构

【解析】 广度优先搜索(BFS)通过逐层扩展节点,通常使用“先进先出”的队列(Queue)来辅助实现。而深度优先搜索(DFS)才使用栈(Stack)或递归辅助实现。

【易错点】 混淆 BFS(队列)和 DFS(栈/递归)的辅助数据结构,这两者决定了搜索遍历的顺序。

9 题(判断题

状态转移方程是动态规划的核心,可以通过递推方式表示问题状态的变化。

正确答案正确

解析详情

【答案】正确

【考点】动态规划核心概念

【解析】 状态转移方程描述了当前状态与子状态(前驱状态)之间的逻辑关系,是动态规划解决问题的数学核心,通常以递推公式的形式展现。

【易错点】 误以为动态规划只能解决最优解问题,实际上也可以解决计数、路径统计等多种具有最优子结构性质的问题。

10 题(判断题

应用动态规划算法时,识别并存储重叠子问题的解是必须的。

正确答案正确

解析详情

【答案】正确

【考点】动态规划算法特征

【解析】 动态规划通过“记忆化”或“填表法”来存储已计算过的子问题结果。当遇到重叠子问题时直接读取存储值,从而避免海量的重复计算,这是动态规划效率优势的核心来源。

【易错点】 误以为所有具有重叠子问题的问题都必须用动态规划,实际上某些情况下贪心算法可能更高效(如果满足贪心选择性质)。