大厂真题 / 华为

华为 2026-10-21 笔试真题 - AI岗

本场考试概述

考试时间:2026年10月21日

考试岗位:AI岗

难度评级:中等

收录范围:材料标注选择题共 20 道,但仅披露 5 道完整单选题;本文如实收录这 5 道及 2 道编程题,不补写其余选择题,也不推测考试时长、分值或未披露的数据上限。

考点分析:

  • 选择题:算子融合、回归评估指标、子空间维度、GPU 存储层次和 RAG 检索。
  • 第一题:确定性 K-Means、中心合并、BIC 选簇数与浮点输出(中等)。
  • 第二题:信息熵、信息增益、ID3 决策树和递归终止规则(中等)。

建议策略:

  • 两题都应先把题面规则列成清单,再写算法;不能直接套用第三方库的默认行为。
  • 聚类重点检查初始化、空簇、距离平局和合并后的实际簇数。
  • 决策树重点检查特征并列、零增益停止、路径内特征去重和类别平票。

选择题:已披露的 5 道

1. 算子融合的核心动机

在深度学习推理框架中,将 Conv2D、BatchNorm 和 ReLU 融合成一个 CUDA Kernel,从底层硬件架构角度看,最核心的工程动机是什么?

  • A. 大幅减少对显存(HBM)的重复读写请求,缓解内存墙瓶颈
  • B. 打破计算密集型任务的算力上限
  • C. 增加 GPU 并发线程数,提高 Occupancy
  • D. 减少 GPU 浮点运算次数,从而降低计算功耗

答案:A。 分开执行时,中间张量可能反复写回显存再被下一算子读取。融合可以让中间值尽量留在寄存器或片上存储中,减少数据搬运,也可能减少 Kernel Launch 开销。它不会提高硬件峰值算力;寄存器使用增加还可能降低 Occupancy。特定融合确实可能简化计算,但本题强调的是避免中间结果的显存往返。

2. 对异常值最敏感的回归指标

下列回归评估指标中,对异常值最敏感的是?

  • A. MSE
  • B. 所有指标敏感度一致
  • C. MAE
  • D. RMSE

材料给出的答案:A。 按常见考题口径,MSE 对残差平方后取平均,比按绝对值计入的 MAE 更强调大误差。

严谨性说明:RMSE 是 MSE 的平方根,同样由平方误差构成,并不是稳健指标;在同一数据集上二者对模型的排序也一致。“最敏感”若不指定尺度、扰动方式或统计定义,不能严格比较不同量纲指标的数值变化。因此保留材料答案,但不把“开根号就消除了异常值影响”作为结论。

3. 子空间的最大维度

若向量空间 $V$ 的维度为 $n$,则 $V$ 的任意子空间维度最大为?

  • A. $0$
  • B. $n$
  • C. $1$
  • D. $n-1$

答案:B。 子空间的线性无关向量组也是原空间的线性无关向量组,维度不能超过 $n$。又因为 $V$ 本身也是它的子空间,所以最大值可以取到 $n$。题目没有要求“真子空间”。

4. HBM 与 SRAM 的访问延迟

在 GPU 显存层次结构中,HBM 与 SRAM(Shared Memory)访存延迟的数量级关系是?

  • A. SRAM 延迟远低于 HBM
  • B. SRAM 延迟比 HBM 高
  • C. HBM 只有读延迟高
  • D. 两者延迟基本持平

答案:A。 Shared Memory 位于片上,HBM 属于片外显存,通常前者访问延迟显著更低。具体周期数依硬件、访问模式和冲突情况而变,不能把某个固定倍数当成所有 GPU 的保证。将数据分块放入片上存储重复利用,是减少高代价显存访问的常见做法。

5. RAG 向量搜索的目的

在 RAG(检索增强生成)中,向量搜索阶段的最终目的是什么?

  • A. 生成答案
  • B. 找到与输入最相似的文档片段
  • C. 对输入进行分词
  • D. 计算相似度得分

答案:B。 向量搜索的输出是召回的相关文档片段,供后续生成使用。相似度计算是检索排序的手段,分词属于输入处理,答案生成则是后续阶段。


第 1 题:X-Means 聚类算法的实现

题目描述

给定 $N$ 个 $d$ 维数据点及允许的簇数范围 $[K_{min},K_{max}]$,对范围内每个簇数运行确定性 K-Means,合并重合中心,再用 BIC 选出最终聚类,输出中心和每个输入点的标签。

这里的“X-Means”按本题定义,是枚举簇数并比较 BIC,不是另行实现文献中基于局部二分裂的版本。

设点 $x_i$ 的标签为 $c(i)$,中心为 $\mu_{c(i)}$,误差平方和为

\[SSE=\sum_{i=1}^{N}\sum_{j=1}^{d}(x_{ij}-\mu_{c(i),j})^2.\]

若合并后的实际簇数为 $k$,使用参数数目 $p=k(d+1)$,评分为

\[BIC=N\ln\left(\frac{SSE}{N}\right)+k(d+1)\ln N.\]

BIC 越小越好。前一项衡量拟合误差,后一项惩罚过多的簇。计算中用 $(SSE+10^{-8})/N$ 保护对数输入。

必须遵守的算法规则

  1. 按点的第一维坐标升序稳定排序;请求 $K>1$ 个簇时,初始中心取排序后下标 $i(N-1)\mathbin{//}(K-1)$ 的点,其中 $i=0,\ldots,K-1$。$K=1$ 单独取排序后的第一个点。
  2. 按欧氏距离平方分配点;距离相等时取编号最小的簇。
  3. 非空簇更新为所属点的均值;空簇保留旧中心。
  4. 交替分配、更新;一轮分配后所有标签不变时收敛。
  5. 先令 $K_{max}=\min(K_{max},N)$;若此时 $K_{min}>K_{max}$,令 $K_{min}=K_{max}$。上下界相同时只运行一次。
  6. 收敛后,中心距离平方严格小于 $10^{-4}$ 的簇需要合并,标签同步映射;合并后的中心使用所属全部点的均值。
  7. 用合并后的实际簇数和 SSE 计算 BIC;BIC 并列时选择较小的枚举 $K$。实现使用 $10^{-9}$ 的比较容差。
  8. 输出前按中心第一维升序重新编号为 $0,1,\ldots,k-1$;第一维相同保持原编号顺序,点标签仍按输入顺序输出。

实现口径:近邻关系不一定具有传递性,本文按材料题解的“归为同一组”实现:在合并前的中心之间连边,合并每个连通分量,然后重新求均值,不反复合并新均值。完全为空的分量保留代表中心。此细节不应被误认为所有 X-Means 实现通用的规则。

输入描述

  • 第一行:两个整数 $N,d$。
  • 接下来 $N$ 行:每行 $d$ 个浮点数,表示一个点。
  • 最后一行:两个整数 $K_{min},K_{max}$。

输出描述

第一行输出实际簇数 $k$;接下来 $k$ 行输出中心,各坐标保留两位小数;最后一行输出 $N$ 个标签。无法聚类时输出 0。

数据范围与边界

  • $1\le N\le 2000$,$1\le d\le 10$。
  • 每个坐标的绝对值不超过 $10^4$。
  • 正常输入保证 $1\le K_{min}\le K_{max}\le N$;材料同时给出越界截断规则,实现仍保留该保护。
  • 材料未给出可保证收敛的固定迭代轮数;以下代码按标签不变停止,不把任意迭代上限说成题面保证。

样例 1

输入

6 2
0.0 0.0
1.0 0.0
5.0 5.0
6.0 5.0
10.0 10.0
11.0 10.0
1 3

输出

3
0.50 0.00
5.50 5.00
10.50 10.00
0 0 1 1 2 2

每两个相邻点组成一个紧密簇,中心是各自的坐标均值。

样例 2

输入

4 2
0.0 0.0
1.0 0.0
10.0 10.0
11.0 10.0
1 3

输出

2
0.50 0.00
10.50 10.00
0 0 1 1

两个簇已能很好地解释数据,继续增加簇带来的误差下降不足以抵消 BIC 的复杂度惩罚。

思路分析

第一步:为什么不能只比较 SSE

簇数增多通常能让点更接近某个中心。极端情况下,每个点单独成簇就能让 SSE 为零,但这样的模型未必有价值。题目给定的 BIC 把误差项与簇数惩罚放在一起比较,因此应完整枚举候选 $K$,不能看到 SSE 下降就立即接受,也不能假定 BIC 关于 $K$ 单调。

各个 $K$ 必须独立初始化、独立迭代,不能直接把上一轮的中心拿来续算,否则就不是题目指定的确定性结果。

第二步:把固定 K 的迭代写准确

分配时只比较距离平方,省去开根号。从小编号向大编号扫描,只有距离严格更小时才替换,这自然实现距离平局取小编号。

更新时累计每个簇的点数和各维坐标和,非空簇取均值,空簇跳过。上一轮已经更新过中心,如果新一轮标签完全相同,那么再更新得到的均值也相同,可以直接结束。

第一维的稳定排序只需预处理一次;初始化时按当前 $K$ 取不同的等距下标。

第三步:合并中心,重新计算 SSE

把合并前的中心看成图的顶点,距离平方小于阈值时连边。使用并查集维护连通分量,并让较小编号作为代表,避免归组结果取决于边的处理顺序。

接着将每个点的旧标签映射到所在分量,重新统计点数和坐标和。合并中心必须是所属点的加权均值,不能对原中心直接等权平均,因为不同簇的点数可能不同。最后用新中心和新标签重新计算 SSE。

此时请求簇数与实际簇数可能不同,甚至实际簇数小于 $K_{min}$;BIC 中应使用实际簇数。

第四步:比较 BIC,再统一输出

从小到大枚举 $K$,仅当新 BIC 小于当前最优值减去 $10^{-9}$ 时替换。这样将容差范围内的差异视为平局,保留先出现的小 $K$。这属于浮点比较约定,并不表示相差极小的两个真实数严格相等。

统一使用 $SSE+10^{-8}$ 与材料题解保持一致。这个平滑项在 SSE 极小时可能影响分数,不能断言它对所有正 SSE 都毫无影响;SSE 为零也不要求全部输入点相同,各簇内点分别重合时同样可能发生。

选定结果后再排序中心并重映射标签。仅在格式化后将字符串 -0.00 改成 0.00,不要提前修改坐标,否则会影响距离和 BIC。

正确性说明

1. 每一轮执行了规定的 K-Means 操作。 固定中心时,每个点选距离平方最小的中心,逐点最小化当前分配的 SSE,并按扫描顺序解决距离平局。固定标签时,对某个非空簇及任意中心 $a$,有

\[\sum_{x\in C}\lVert x-a\rVert^2 =\sum_{x\in C}\lVert x-\bar x\rVert^2 +\lvert C\rvert\cdot\lVert a-\bar x\rVert^2.\]

其中 $\bar x$ 为均值,故均值最小化该簇的 SSE;空簇对 SSE 无贡献,保留旧中心符合规则。因此交替操作不会增加 SSE。标签不变时,均值也不变,结果满足指定的停止条件。

2. 合并符合分组口径。 并查集恰好连接所有满足阈值的中心对,其分量就是规定的合并组。重映射后每个点仍恰好属于一个组;按该组全部点求均值,得到一致的中心、标签和 SSE。

3. 枚举保留最优候选。 每轮得到的 BIC 都使用合并后的实际簇数。按约定的浮点比较规则维护最优值,枚举结束后保留评分最优的候选,容差内并列保留较小的请求 $K$。最后的重编号只改变名称,不改变聚类。

这里证明的是实现符合本题算法,不是证明 K-Means 求得全局最小 SSE,也不保证 BIC 选出的簇数一定等于数据的真实类别数。

题解代码

仅使用 Python 标准库,ACM 输入输出。为便于核对,代码将 K-Means、中心合并和模型选择分开实现。

import sys
import math

MERGE_EPS = 1e-4
BIC_EPS = 1e-9


def dist2(a, b):
    value = 0.0
    for x, y in zip(a, b):
        delta = x - y
        value += delta * delta
    return value


def kmeans(points, order, d, k):
    n = len(points)
    centers = []
    for i in range(k):
        pos = 0 if k == 1 else i * (n - 1) // (k - 1)
        centers.append(points[order[pos]][:])

    labels = [-1] * n
    while True:
        new_labels = []
        for point in points:
            best = 0
            best_dist = dist2(point, centers[0])
            for c in range(1, k):
                current = dist2(point, centers[c])
                if current < best_dist:
                    best, best_dist = c, current
            new_labels.append(best)

        if new_labels == labels:
            return centers, labels
        labels = new_labels

        counts = [0] * k
        totals = [[0.0] * d for _ in range(k)]
        for point, c in zip(points, labels):
            counts[c] += 1
            for j in range(d):
                totals[c][j] += point[j]
        for c in range(k):
            if counts[c]:
                centers[c] = [v / counts[c] for v in totals[c]]


def merge_centers(points, centers, labels, d):
    k = len(centers)
    parent = list(range(k))

    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]
            x = parent[x]
        return x

    # 所有距离都使用合并前的中心,不在枚举中修改中心。
    for a in range(k):
        for b in range(a + 1, k):
            if dist2(centers[a], centers[b]) < MERGE_EPS:
                ra, rb = find(a), find(b)
                if ra != rb:
                    parent[max(ra, rb)] = min(ra, rb)

    roots = [find(c) for c in range(k)]
    representatives = sorted(set(roots))
    number = {root: i for i, root in enumerate(representatives)}
    new_labels = [number[roots[c]] for c in labels]
    size = len(representatives)
    counts = [0] * size
    totals = [[0.0] * d for _ in range(size)]
    for point, c in zip(points, new_labels):
        counts[c] += 1
        for j in range(d):
            totals[c][j] += point[j]

    merged = []
    for c, root in enumerate(representatives):
        if counts[c]:
            merged.append([v / counts[c] for v in totals[c]])
        else:
            merged.append(centers[root][:])
    return merged, new_labels


def main():
    stream = iter(sys.stdin.buffer.read().split())
    first = next(stream, None)
    if first is None:
        return
    n, d = int(first), int(next(stream))
    if n <= 0 or d <= 0:
        print(0)
        return
    points = [[float(next(stream)) for _ in range(d)] for _ in range(n)]
    kmin, kmax = int(next(stream)), int(next(stream))
    kmax = min(kmax, n)
    kmin = min(kmin, kmax)
    if kmin < 1:
        print(0)
        return

    order = sorted(range(n), key=lambda i: points[i][0])
    best_bic = math.inf
    best_centers = best_labels = None
    for k in range(kmin, kmax + 1):
        centers, labels = kmeans(points, order, d, k)
        centers, labels = merge_centers(points, centers, labels, d)
        sse = sum(dist2(point, centers[c])
                  for point, c in zip(points, labels))
        bic = (n * math.log((sse + 1e-8) / n)
               + len(centers) * (d + 1) * math.log(n))
        if bic < best_bic - BIC_EPS:
            best_bic = bic
            best_centers, best_labels = centers, labels

    order = sorted(range(len(best_centers)),
                   key=lambda c: (best_centers[c][0], c))
    remap = {old: new for new, old in enumerate(order)}
    print(len(order))
    for c in order:
        row = []
        for value in best_centers[c]:
            text = f"{value:.2f}"
            row.append("0.00" if text == "-0.00" else text)
        print(" ".join(row))
    print(" ".join(str(remap[c]) for c in best_labels))


if __name__ == "__main__":
    main()

复杂度分析

记候选簇数为 $K$,其 K-Means 分配轮数为 $T_K$。

  • 排序预处理:$O(N\log N)$。
  • 固定 $K$ 的分配、更新:$O(T_KNKd)$。
  • 合并时检查中心对的距离:$O(K^2d)$;并查集使用路径压缩、按较小编号连接,未使用按秩合并,保守另计 $O(K^2\log(K+1))$ 的摊还上界。
  • 重算均值与 SSE:$O(Nd+Kd)$;最终排序和重编号不超过 $O(K_{max}\log K_{max}+N)$。

时间复杂度:总时间可保守写为

\[O\left(N\log N+\sum_{K=K_{min}}^{K_{max}} \left[T_KNKd+K^2\bigl(d+\log(K+1)\bigr)\right]\right).\]

空间复杂度:$O(Nd+K_{max}d+N)=O(Nd)$,因为截断后 $K_{max}\le N$。只保留当前候选和最佳候选,不存储全部枚举结果。

性能边界:若允许枚举从小簇数直到 $N$,该纯 Python 直接实现的最坏代价较高。材料未披露时间限制、总迭代预算或更小的枚举跨度,不能仅凭样例通过就声称覆盖最大规模的运行时间保证;也不能为了加速擅自提前停止枚举。


第 2 题:网游皮肤智能推荐系统

题目描述

某款 5v5 竞技游戏希望根据玩家行为,为玩家推荐更可能购买的英雄皮肤。给定训练样本,按题目指定的信息增益规则构建二叉决策树,再预测每个推理样本是否购买。

每个样本包含三个布尔特征,输入用 0 表示 False、1 表示 True:

编号 特征 说明
0 是否 VIP 会员 是否购买 VIP 会员或月卡
1 是否购买过该英雄皮肤 是否有该英雄的皮肤购买记录
2 是否熟练英雄 是否有该英雄达到最高段位王者的记录

类别 0 表示预测不购买、不推荐;类别 1 表示预测购买、推荐。

熵与信息增益

设当前节点的非空训练集为 $S$,两类占比分别为 $p_0,p_1$,信息熵为

\[H(S)=-\sum_{c\in\{0,1\}}p_c\log_2p_c.\]

约定 $0\log_2 0=0$,实现跳过计数为零的项。对特征 $A$,令 $S_v$ 是特征值为 $v$ 的样本子集,则

\[H(S\mid A)=\sum_{v\in\{0,1\}} \frac{\lvert S_v\rvert}{\lvert S\rvert}H(S_v), \qquad IG(S,A)=H(S)-H(S\mid A).\]

空子集的加权项记为零。

建树与预测规则

  1. 在当前路径未使用过的特征中,选择信息增益最大的特征。
  2. 多个特征并列最大时,选择编号最小者。
  3. 样本全属于同一类别时,生成该类别的叶子。
  4. 特征用尽,或剩余特征的信息增益均不大于零时,生成多数类别叶子;两类数量相同时取 0。
  5. 特征值为 0 的样本进入左子树,为 1 的进入右子树。
  6. 对推理样本,从根按对应特征值走到叶子,输出叶子类别。

浮点实现采用 $10^{-9}$ 容差判断增益并列与非正增益。空子集额外以父节点的多数类别兜底;在严格正信息增益分裂下,两边本应均非空。

输入描述

  • 第一行:训练样本数 $N$ 和推理样本数 $M$。
  • 接下来 $N$ 行:每行四个 0/1 整数,依次为特征 $0,1,2$ 和类别标签。
  • 接下来 $M$ 行:每行三个 0/1 整数,为待预测的三个特征。

输出描述

输出 $M$ 行,每行一个 0 或 1,对应输入顺序下的预测结果。

数据范围与边界

特征数固定为三个,特征值和类别均为 0/1。材料没有披露 $N,M$ 的具体数值上限,因此不另造范围。相同特征组合可以对应不同标签,应保留样本频次参与熵和多数表决,不能去重。

输入格式示意(材料未附输出)

4 4
1 1 1 1
1 1 0 0
1 1 1 0
0 0 0 0
1 1 1
1 1 0
1 0 1
0 0 1

这段数据用于说明“训练行有四列、推理行有三列”,不另补一个所谓原始样例输出。以下才是材料给出的两组完整输入输出样例。

样例 1

输入

4 4
1 1 1 0
1 1 0 0
1 0 1 1
0 0 0 1
1 1 1
1 1 0
1 0 0
0 0 1

输出

0
0
1
1

训练数据可以按特征 1 完全分开:该特征为 1 时标签均为 0,为 0 时标签均为 1,因此四个预测依次为 0,0,1,1。

样例 2

输入

4 4
1 1 1 1
1 1 0 1
1 0 1 0
0 0 0 0
1 1 1
1 1 0
1 0 0
0 0 1

输出

1
1
0
0

这一组同样按特征 1 完全分开,但两边类别与样例 1 相反。

思路分析

第一步:从“分开后是否更纯”理解信息增益

一个节点如果全是同一类,已经不需要再问玩家特征;两类混杂时,才需要找一个有区分能力的特征。熵刻画混杂程度,信息增益比较分裂前后的熵差。

条件熵必须按子集大小加权,不能把左右熵直接取平均,否则一个样本的分支会和大量样本的分支拥有相同权重。由于三个特征都为布尔值,每个特征只需统计左右分支中两类的数量,即可计算增益。

这里不是搜索所有可能的决策树来求全局最优,而是在每个节点执行题目规定的贪心特征选择。

第二步:明确平局与停止条件

从小编号开始检查特征,仅当增益超过当前最优值加上容差时替换,因此近似并列保留小编号。

以下情况直接生成叶子:样本全同类、可用特征为空、最大增益不为正。后两种情况下取多数类别,只有正例严格多于反例才输出 1,平票必须输出 0。

零增益不等于以后永远无法分类。 某些组合关系可能让每个单独特征的信息增益都为零,但联合起来能区分标签。本题明确要求此时停止,因此不能为了拟合训练集而继续向下分裂。

第三步:特征集合必须属于当前路径

选择特征后,从可用集合中删除它,再将剩余集合传给两个子树。使用不可变元组,可以避免全局 used 状态忘记回溯的问题。

某个特征在左分支用过,不代表右分支也不能使用;只有祖先节点已经使用的特征才应在两边共同排除。

第四步:预测仅需沿树行走

叶子直接保存整数 0/1;内部节点保存 (特征编号, 左子树, 右子树)。每次读取样本对应特征,为 0 进入左边,为 1 进入右边,直到叶子。

正确性证明

按当前路径剩余特征数归纳。

  • 若样本全同类,代码输出该类别;若没有剩余特征或所有增益均不为正,代码按多数表决输出,平票为 0。这些都是题目指定的终止规则。
  • 否则,代码对每个可用特征计算两个子集的类别计数,得到规定的熵、条件熵和信息增益,并按容差与编号规则选出分裂特征。因此当前节点选择正确。
  • 左右子树分别接收特征为 0/1 的全部样本,且剩余特征数减少。由归纳假设,两个子树均按题意构建,故当前子树也正确。
  • 预测时,每一步恰好根据当前节点的特征选择规定分支,最终返回对应叶子的类别,因此预测结果符合所建决策树。

每条路径至多使用全部三个特征,递归必然终止。该证明不意味着贪心 ID3 得到最小决策树,或能保证未见样本的真实购买行为。

题解代码

import sys
import math

EPS = 1e-9


def entropy(positive, total):
    if total == 0 or positive == 0 or positive == total:
        return 0.0
    p = positive / total
    q = (total - positive) / total
    return -p * math.log2(p) - q * math.log2(q)


def build(rows, features, fallback=0):
    if not rows:
        return fallback
    n = len(rows)
    positive = sum(row[3] for row in rows)
    label = int(positive > n - positive)
    if positive == 0 or positive == n or not features:
        return label

    base = entropy(positive, n)
    best_feature = -1
    best_gain = -math.inf
    for feature in features:
        count = [0, 0]
        pos = [0, 0]
        for row in rows:
            value = row[feature]
            count[value] += 1
            pos[value] += row[3]
        conditional = sum(count[v] / n * entropy(pos[v], count[v])
                          for v in (0, 1))
        gain = base - conditional
        if gain > best_gain + EPS:
            best_gain = gain
            best_feature = feature

    if best_gain <= EPS:
        return label
    remaining = tuple(f for f in features if f != best_feature)
    left_rows = [row for row in rows if row[best_feature] == 0]
    right_rows = [row for row in rows if row[best_feature] == 1]
    left = build(left_rows, remaining, label)
    right = build(right_rows, remaining, label)
    return best_feature, left, right


def predict(tree, sample):
    while isinstance(tree, tuple):
        feature, left, right = tree
        tree = right if sample[feature] == 1 else left
    return tree


def main():
    read = sys.stdin.buffer.readline
    first = read().split()
    if not first:
        return
    n, m = map(int, first)
    train = [tuple(map(int, read().split())) for _ in range(n)]
    tree = build(train, (0, 1, 2))
    for _ in range(m):
        sample = tuple(map(int, read().split()))
        print(predict(tree, sample))


if __name__ == "__main__":
    main()

复杂度分析

记一般情形的特征数为 $F$。每层节点的样本集合互不相交,总样本数至多为 $N$;每个节点需要扫描其剩余特征,因此建树时间可界为 $O(NF^2)$。每个样本预测至多经过 $F$ 次分支,总预测时间为 $O(MF)$。

本题 $F=3$ 固定,因此:

时间复杂度:$O(N+M)$。

空间复杂度:$O(N)$。训练数据和递归子集占线性空间,树的深度有固定上界;推理数据逐行处理,不额外保存全部 $M$ 个样本。


小结

  • 第一题是数值算法的规则实现题:固定初始化、距离平局、空簇处理、中心合并、BIC 参数及标签重排共同决定结果。不能把局部收敛写成全局最优,也不能忽略枚举范围带来的运行代价。
  • 第二题是确定性 ID3 建树题:条件熵要加权,特征集合属于路径,零增益停止和平票取 0 都是输出的一部分。
  • 材料边界必须说明:选择题仅披露 5 道;第二题没有给出样本数上限;指标“敏感度”、近邻中心合并和浮点容差都应明确语义,而不是包装成无条件的数学结论。