要高考了,我也只剩一百天了。
Q:介绍数据结构二叉树?
第一反应:二叉树就是每个节点有且只有最多两个孩子的节点,分别记作左孩子和有孩子,所以这个node可以定义为指向父节点的指针、指向左、右节点的指针,还有自己的value值。然后完全二叉树的性质相对较多,比如树的节点个数向下取整log2+1就是深度,这些都可以用数学简单证明出来。
AI表示面试官希望听到二叉树的优势:"二叉树的每个节点度数 ≤ 2,这个结构决定了它可以用数组连续存储(完全二叉树),也可以用链式存储(指针)。更重要的是,任何多叉树都可以用'左孩子右兄弟'表示法转换成一棵二叉树,所以二叉树是树结构里通用性最强、实现最方便的一种形式。"
Q:二叉树的最大深度你知道吧(老师还解释了一下),你怎么求?
我的第一反应:链式存储的就用DFS,数组存储的可以直接找到最大的下标,然后取log2+1。
"求二叉树的最大深度,可以根据存储结构用不同的方法。
如果是链式存储的通用二叉树,我会用深度优先搜索(DFS)递归地去算。核心逻辑就是:树的深度 = max(左子树的深度, 右子树的深度) + 1。这个解法的时间复杂度是O(n)。
如果明确知道树是用数组存储的完全二叉树,并且节点是从下标1开始连续存放的,那就可以直接用公式计算:最大深度 = floor(log2(节点数)) + 1。这是一种利用完全二叉树性质的数学解法,可以直接算出来,不需要遍历。"
Q:你了解深度学习吧?有没有用过降维的什么算法?
这里帖主写的PCA,我也跟着复习一遍PCA。
PCA 是一种线性降维算法。核心是找到能让数据方差最大的投影方向,把数据投影到这些方向上去。
具体做法是:
- 对数据中心化
- 计算协方差矩阵
- 对协方差矩阵做特征分解,得到特征值和特征向量
- 按特征值从大到小排序,取前 k 个特征向量作为新坐标轴
- 把原始数据投影到这些轴上,得到降维结果
优点是算法简单、线性计算快,缺点是不擅长处理非线性数据。
Q:映射回去是怎么做的?
映射矩阵的转置
Q:说一下图神经网络层与层之间是如何相连的?
"图神经网络的层与层之间通过消息传递机制相连。每一层,每个节点根据邻接矩阵找到它的邻居(包括自己),把邻居的特征进行聚合(如求平均、加权和或注意力加权),然后通过一层线性变换和非线性激活,得到该层新的节点表示。多层堆叠后,节点就能逐步融合更大邻域的信息。"
Q:C语言预编译是什么?
一、预编译做什么?(4件事)
| 预处理指令 | 作用 | 例子 |
|---|---|---|
#include |
文件包含:把头文件内容原封不动地插入到当前位置 | #include <stdio.h> |
#define |
宏定义:把代码中的宏名替换成对应的值或表达式 | #define PI 3.14159 |
#if / #ifdef / #endif |
条件编译:根据条件决定保留还是删除某段代码 | #ifdef DEBUG ... #endif |
#pragma |
编译器特殊指令 | #pragma once |
- 宏定义和函数的区别?
宏是文本替换,在预编译阶段完成,没有函数调用开销;但宏不检查类型,且参数会重复计算(如 SQUARE(x++) 有问题)。函数在编译阶段处理,有类型检查。
- #include <stdio.h> 和 #include "myheader.h" 的区别?
<> 只在系统默认路径里找(如 /usr/include)。"" 先在当前目录找,找不到再去系统路径找。
- #ifndef HEADER_H 这种写法是干什么用的?
防止头文件被重复包含。第一次包含时 HEADER_H 未定义,进入 #define;第二次包含时已经定义,跳过整个文件内容。
六、面试回答模板
"C语言的预编译是编译的第一个阶段,处理所有以 # 开头的预处理指令,包括文件包含(#include)、宏替换(#define)、条件编译(#if)等。这一步是纯文本层面的处理,不涉及任何C语言的语法检查。预编译完成后,输出一个没有预处理指令的 .i 文件,再交给编译器进行真正的编译。"
Q:好,那问你操作系统,你知道进程上下文是什么么?
A:"进程上下文是操作系统为了支持多任务、实现进程切换而保存的一组'状态快照'。
它主要包含三个部分:一是寄存器的值,特别是程序计数器;二是内存管理信息,主要是页表地址;三是内核中与该进程相关的数据结构。
当操作系统决定切换到另一个进程时,它做的就是'保存当前进程的上下文' -> '恢复下一个进程的上下文'这个动作。这个'上下文切换'的过程是理解操作系统调度和性能开销的关键。"
Q:文件读写r和w的区别
| 模式 | 全称 | 文件必须存在 | 原有内容 | 读写位置 |
|---|---|---|---|---|
| r | read(读) | ✅ 必须存在 | 保留 | 文件开头 |
| w | write(写) | ❌ 不存在则创建 | 清空 | 文件开头 |
关键区别:
-
r是只读,不能写;w是只写,不能读 -
w会清空文件原有内容 (如果文件存在),r不会
常见组合:
-
r+:读写,文件必须存在,不清空 -
w+:读写,文件不存在则创建,存在则清空 -
a:追加写,文件不存在则创建,不清空,写入位置在末尾
Q:fopen返回什么
返回 FILE*类型的指针(文件指针),指向一个 FILE 结构体。
这个结构体里包含了:
- 文件描述符
- 当前读写位置
- 缓冲区指针
- 错误标志、文件结束标志等
返回值判断:
- ✅ 成功:返回有效的 FILE* 指针
- ❌ 失败:返回 NULL(例如用 r 打开一个不存在的文件)
典型用法:
cpp
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return -1;
}
Q:既然有了整数和双精度浮点数,为什么还要存在单精度浮点数?
一句话回答 :用更少的内存和更快的速度,换取较低的精度------在不需要高精度的场景下更高效。
三种浮点数对比
| 类型 | 字节数 | 有效十进制位数 | 取值范围 |
|---|---|---|---|
| float(单精度) | 4 字节 | 6-7 位 | ±1.18e-38 ~ ±3.4e38 |
| double(双精度) | 8 字节 | 15-16 位 | ±2.23e-308 ~ ±1.80e308 |
| long double | 16 字节 | 18-21 位 | 更大 |
为什么需要 float?
| 原因 | 解释 |
|---|---|
| 节省内存 | 4GB 内存存 1 亿个 double 要 800MB,float 只要 400MB |
| 内存带宽 | 读 4 字节比 8 字节快,数据密集型任务(如图像处理)吞吐量翻倍 |
| SIMD 向量化 | AVX 指令集一次可处理 8 个 float 但只能处理 4 个 double |
| GPU 友好 | GPU 对 float 的计算单元远超 double(如游戏显卡双精度只有单精度的 1/32) |
| 深度学习 | 模型参数量大,用 float16 或 bfloat16 甚至 int8 来加速(NVIDIA Tensor Core) |
"单精度浮点数存在的主要原因是:用一半的内存和更高的速度换取足够用的精度。
在图像处理、3D 图形、深度学习等大规模并行计算的场景下,内存带宽和计算速度往往是瓶颈。使用 float 可以让数据传输量减半、向量化指令一次处理更多数据,GPU 上 float 的计算单元也远多于 double。
double 适用于需要高精度的科学计算,但对于绝大多数视觉和 AI 任务,float 的 6-7 位有效数字已经足够。"