GESP 客观题评测系统

2025-12-Level-6

2025-12-Level-6

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

单选题(每题 2 分)

1 题(单选题

在面向对象编程中,下列关于虚函数的描述中,错误的是()。

A.
虚函数用于支持运行时多态
B.
通过基类指针调用虚函数时,会根据对象实际类型决定调用版本
C.
构造函数可以声明为虚函数以支持多态
D.
基类析构函数常声明为虚函数以避免资源泄漏

正确答案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;
    }
}
A.
继承
B.
封装
C.
多态
D.
链接

正确答案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;
    }
}
A.
执行代码会输出两行,内容分别为:钢琴:叮咚叮咚 和 吉他:咚咚当当
B.
执行代码会输出两行,内容分别为:乐器在演奏声音 和 乐器在演奏声音
C.
代码编译出现错误
D.
代码运行出现错误

正确答案C

解析详情

【答案】C

【考点】虚函数;override 关键字;编译检查

【解析】 在 C++ 中,override 关键字用于显式声明重写基类的虚函数。如果基类中对应的函数没有被声明为 virtual,编译器会报错。本题基类 play() 缺少 virtual 关键字,因此派生类的 override 声明会导致编译失败。

【易错点】忽略 override 对基类虚函数声明的强制依赖。

4 题(单选题

某文本编辑器把用户输入的字符依次压入栈 S。用户依次输入 A,B,C,D 后,用户按了两次撤销(每次撤销,弹出栈顶一个字符)。此时栈从栈底到栈顶的内容是:()。

A.
A B
B.
A B C
C.
A B D
D.
B C

正确答案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)的值是()。

A.
(2, 5)
B.
(2, 0)
C.
(3, 5)
D.
(3, 0)

正确答案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;
}
A.
满二叉树
B.
完全二叉树
C.
二叉搜索树
D.
平衡二叉树

正确答案B

解析详情

【答案】B

【考点】二叉树分类;层序遍历;完全二叉树判定

【解析】 该函数使用队列进行层序遍历。hasNull 变量记录是否遇到了空结点。在完全二叉树中,空结点只能出现在序列末尾。如果遇到空结点后又出现了非空结点(hasNull 为真且 cur 不为空),则说明不是完全二叉树。

【易错点】分不清完全二叉树和满二叉树的层序特征。

7 题(单选题

以下代码实现了二叉树的()。

void traverse(TreeNode* root) {
    if (!root) return;
    traverse(root->left);
    traverse(root->right);
    cout << root->val << " ";
}
A.
前序遍历
B.
中序遍历
C.
后序遍历
D.
层序遍历

正确答案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;
}
A.
nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, -1));
internalIdx.push_back(z);
B.
nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, -1));
leafIdx.push_back(z);
C.
internalIdx.push_back(z);
nodes.push_back(Node(nodes[x].w + nodes[y].w, x, y, x + y));
D.
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 题(单选题

以下关于哈夫曼编码的说法,正确的是()。

A.
哈夫曼编码是定长编码
B.
哈夫曼编码中,没有任何一个字符的编码是另一个字符编码的前缀
C.
哈夫曼编码一定唯一
D.
哈夫曼编码不能用于数据压缩

正确答案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;
}
A.
查找
B.
插入
C.
删除
D.
遍历

正确答案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.
if (node->left) st.push(node->left);
B.
if (node->left) st.pop(node->left);
C.
if (node->left) st.front(node->left);
D.
if (node->left) 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;
}
A.
q.push(cur);
B.
if (cur->right) q.push(cur->right);
C.
if (cur->left) q.push(cur->left);
if (cur->right) q.push(cur->right);
D.
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;
}
A.
最坏情况下,访问结点数是O(logn)O(\log n)
B.
最坏情况下,访问结点数是O(n)O(n)
C.
无论如何,访问结点数都不超过树高的一半
D.
一定比在普通二叉树中搜索快

正确答案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);
A.
内层 j 必须从小到大,否则会漏解
B.
内层 j 必须从大到小,否则同一件物品会被用多次
C.
j 从大到小或从小到大都一样
D.
只要 dp 初始为 0,方向无所谓

正确答案B

解析详情

【答案】B

【考点】0/1 背包问题;动态规划;一维空间优化

【解析】 在 0/1 背包的一维实现中,外层循环遍历物品,内层循环遍历容量。为了确保每件物品只被选一次,更新 dp[j] 时必须使用上一轮物品的状态,因此 j 必须从大到小逆序遍历。正序遍历会导致同一物品被多次选取。

【易错点】不理解容量循环方向与物品选取次数的关系。

15 题(单选题

以下关于动态规划的说法中,错误的是()。

A.
动态规划方法通常能够列出递推公式。
B.
动态规划方法的时间复杂度通常为状态的个数。
C.
动态规划方法有递推和递归两种实现形式。
D.
对很多问题,递推实现和递归实现动态规划方法的时间复杂度相当。

正确答案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 个节点的完全二叉树,则树的深度为log2(n)+1\lfloor \log_{2}(n) \rfloor + 1

正确答案正确

解析详情

【答案】正确

【考点】完全二叉树;树高计算;地板函数

【解析】 对于有 n 个结点的完全二叉树,如果根深度为 1,则深度 h 的公式为 floor(log2(n)) + 1。例如 n=1 时深度为 1,n=3 时深度为 2,均符合该公式。

【易错点】记混 floor 和 ceil 在树高公式中的使用条件。