1 - 复杂度分析
1
函数 fun()
的时间复杂度是多少?( )
int fun(int n) {
int count = 0;
for (int i = n; i > 0; i /= 2)
for (int j = 0; j < i; j++)
count += 1;
return count;
}
对于输入整数 $n$,fun()
的最外层循环执行 $\log(n)$ 次,而 fun()
的最内层语句的执行次数如下:
$$ n + \frac{n}{2} + \frac{n}{4} + \cdots + 1 $$
该级数是一个等比数列求和,其结果为 $2n$。因此时间复杂度 $T(n)$ 可以表示为:
$$ T(n) = O(\log(n)) \times O(2n) = O(n \cdot \log(n)) $$
补充说明:
- 外层循环每次将 $i$ 除以 2,共执行 $\log_2(n)$ 次
- 内层循环总迭代次数构成等比数列求和
- 最终时间复杂度由乘积项决定
count
的最终值等于内层循环总执行次数
2
函数 fun()
的时间复杂度是多少?
int fun(int n) {
int count = 0;
for (int i = 0; i < n; i++)
for (int j = i; j > 0; j--)
count = count + 1;
return count;
}
通过计算表达式 count = count + 1;
的执行次数可以得出时间复杂度。该表达式的执行次数为 0 + 1 + 2 + 3 + 4 + … + (n-1) 次。因此时间复杂度为:
$$ O(0 + 1 + 2 + 3 + \cdots + n-1) = O\left(\frac{n(n-1)}{2}\right) = O(n^2) $$
3
汉诺塔问题中,n 个圆盘的最优时间递推关系式是( )
解决汉诺塔问题的递归步骤如下。设三根柱子分别为 A、B 和 C,目标是将 n 个圆盘从 A 移动到 C。
具体步骤为:
- 将 n-1 个圆盘从 A 移动到 B,此时 A 上仅剩第 n 个圆盘
- 将第 n 个圆盘从 A 移动到 C
- 将 n-1 个圆盘从 B 移动到 C,叠放在第 n 个圆盘之上
上述递归解法的时间复杂度递推函数 T(n) 可表示为:
$$ T(n) = 2T(n-1) + 1 $$
4
下面函数的时间复杂度是多少?
void fun(int n, int arr[]) {
int i = 0, j = 0;
for (; i < n; ++i)
while (j < n && arr[i] < arr[j])
j++;
}
- 关键观察:变量
j
在整个函数执行过程中仅初始化一次,不会随i
的变化而重置。 - 行为分析:
内层while
循环的总迭代次数取决于j
的移动方向。由于j
始终递增且不超过n
,其最大累计移动次数为n
次。 - 与变体对比:
若将函数改为以下形式(j
被重置为 0):此时内层循环会因每次void fun(int n, int arr[]) { int i = 0, j = 0; for (; i < n; ++i) { j = 0; while (j < n && arr[i] < arr[j]) j++; } }
i
变化而重新遍历数组,导致时间复杂度退化为 O(n²)。 - 结论:原函数中
j
的单向移动特性使得整体时间复杂度为 O(n)。
5
在一场竞赛中,观察到四个不同的函数。所有函数都使用单个 for 循环,且循环内部执行相同的语句集。考虑以下 for 循环:
A) for(i=0;i<n;i++)
B) for(i=0;i<n;i+=2)
C) for(i=1;i<n;i*=2)
D) for(i=n;i<=n;i/=2)
若 $n$ 是输入规模(正数),哪个函数最高效(假设任务本身不是问题)?( )
A)
for(i=0;i<n;i++)
时间复杂度为 $O(n)$。循环从 0 到 $n-1$,每次递增 1,共执行 $n$ 次。B)
for(i=0;i<n;i+=2)
时间复杂度为 $O(n/2)$。虽然每次步长为 2,但在渐近分析中与 $O(n)$ 等价。C)
for(i=1;i<n;i*=2)
时间复杂度为 $O(\log n)$。指数增长使得迭代次数随 $n$ 增大呈对数级增长。D)
for(i=n;i<=n;i/=2)
此循环为 无限循环。初始值 $i=n$ 满足 $i \leq n$,但除以 2 后 $i$ 会逐渐减小,始终满足循环条件。
综上,C 的时间复杂度最低,因此最高效。
6
当说算法 X 比 Y 渐近更高效时,其含义是什么?( )
当我们说算法 X 比 Y 渐近更高效时,意味着随着算法输入规模的不断增大,X 的运行时间最终会比 Y 的运行时间更快。这通常通过大 O 表示法(big O notation)来描述,它给出了算法最坏情况运行时间的上界。具体来说:
- 如果 X 比 Y 渐近更高效,则存在一个常数 c,使得对于所有足够大的输入,X 的运行时间 ≤ c × Y 的运行时间;
- 即 X 的大 O 复杂度类低于 Y(如 $O(n)$ vs $O(n^2)$);
- 这表明在 大输入情况下,X 在效率上始终优于 Y;
- 然而,对于 小输入,Y 可能仍然比 X 更快(因常数因子或低阶项影响)。
因此,我们不能断言 X 是所有输入情况下的更好选择,但可以说 X 是除可能的小输入外所有输入情况下的更好选择。
7
在以下 C 函数中,假设 $ n \geq m $。
int gcd(n, m) {
if (n % m == 0)
return m;
n = n % m;
return gcd(m, n);
}
该函数会进行多少次递归调用?
- 上述代码是用于计算最大公约数(GCD)的 欧几里得算法 的实现。
- 欧几里得算法的核心思想是:若 $ a > b $,则 $ \gcd(a, b) = \gcd(b, a \bmod b) $。
- 在最坏情况下,递归次数与斐波那契数列相邻两项的比值有关,其时间复杂度为 $O(\log n) $。
- 因此,选项 A 正确。
8
考虑以下函数:
int unknown(int n) {
int i, j, k = 0;
for (i = n/2; i <= n; i++)
for (j = 2; j <= n; j = j * 2)
k = k + n/2;
return k;
}
该函数的时间复杂度是多少?( )
- 外层循环:执行次数为
n - n/2 + 1 ≈ n/2
次,即 Θ(n) - 内层循环:
j
每次乘以 2 直至超过n
,执行次数为log₂n
次 - 总时间复杂度:外层循环次数 × 内层循环次数 = Θ(n) × log n = O(n log n)
9
考虑以下两个函数。它们的时间复杂度是多少?
int fun1(int n) {
if (n <= 1)
return n;
return 2 * fun1(n - 1);
}
int fun2(int n) {
if (n <= 1)
return n;
return fun2(n - 1) + fun2(n - 1);
}
fun1() 分析
递归关系式:$ T(n) = T(n-1) + C $
时间复杂度:$ O(n) $(线性递归)fun2() 分析
递归关系式:$ T(n) = 2T(n-1) + C $
时间复杂度:$ O(2^n) $(指数级递归树)
关键区别:
fun1()
每次递归仅调用自身一次,形成单链递归结构;fun2()
每次递归调用自身两次,形成二叉树状递归结构,导致子问题数量呈指数增长。
10
当使用二分查找计算待插入数据的位置时,插入排序的最坏情况时间复杂度是多少?( )
使用二分查找确定插入位置不会降低插入排序的时间复杂度。这是因为插入操作包含两个步骤:
计算插入位置
- 使用二分查找可将此步骤时间复杂度从 O(N) 降低至 O(log N)
将插入位置后的数据向右移动一位
- 此步骤始终需要 O(N) 时间复杂度
尽管第一步通过二分查找优化了时间效率,但第二步的数据移动操作仍是决定性因素。由于每次插入都需要进行一次完整的数组移动(最坏情况下),总共有 N 次插入操作,因此整体时间复杂度仍为 O(N²)。
11
考虑以下 C 语言程序片段,其中 i
、j
和 n
是整型变量。
for(i = n, j = 0; i > 0; i /= 2, j += i);
设 val(j)
表示 for 循环终止后变量 j
中存储的值。以下哪一项是正确的?
变量 j 初始为 0,其值是 i 在每次迭代中的值之和。i 初始化为 n,并在每次迭代中减半。
关键分析:
j 的累加过程为:$$ j = \frac{n}{2} + \frac{n}{4} + \frac{n}{8} + \dots + 1 $$
这是一个等比数列求和,总和为 $ n - 1 $(当 $ n $ 为 $ 2^k $ 时),因此时间复杂度为 $O(n)$。
注意事项:
for
循环后的分号表示循环体为空,所有操作通过初始化和条件更新完成。
12
要找出 100 个数中的最小值和最大值所需的最少比较次数是( )。
在 n 个数中找出最小值和最大值的步骤:
- 取两个元素 (a, b),比较它们(假设 a > b)
- 通过比较 (min, b) 更新最小值
- 通过比较 (max, a) 更新最大值
因此,每处理两个元素需要 3 次比较,总比较次数为 $\frac{3n}{2} - 2$,因为第一步不需要更新 min 或 max。
递推关系式为:
$$
\begin{align*}
T(n) &= T(\lceil \frac{n}{2} \rceil) + T(\lfloor \frac{n}{2} \rfloor) + 2 \\
&= 2T(\frac{n}{2}) + 2 \\
&= \lceil \frac{3n}{2} \rceil - 2
\end{align*}
$$
代入 $n=100$ 得:$\frac{3 \times 100}{2} - 2 = 148$,即答案。
13
考虑以下 C 函数:
double foo(int n) {
int i;
double sum;
if (n == 0)
return 1.0;
else {
sum = 0.0;
for (i = 0; i < n; i++)
sum += foo(i);
return sum;
}
}
上述函数的空间复杂度是( ):
解析:
- 函数
foo()
是递归实现的,每次调用会创建新的栈帧 - 最坏情况下(当
n
为最大值时),递归调用链深度达到n
层 - 栈空间消耗与递归深度成正比,因此空间复杂度为 O(n)
- 其他选项分析:
- O(1) 表示常数空间,不符合递归特性
- O(n!) 和 O(n^n) 是时间复杂度的典型表现,而非空间复杂度
14
两个矩阵 M1 和 M2 分别存储在数组 A 和 B 中。每个数组可以按行优先或列优先的顺序连续存储在内存中。计算 M1 × M2 的算法的时间复杂度将如何( )?
这是一个陷阱题。注意问题询问的是时间复杂度,而非程序实际运行时间。对于时间复杂度而言,数组元素的存储方式并不影响结果,因为:
- 矩阵乘法始终需要访问相同数量的 M1 和 M2 元素
- 数组元素访问始终是常数时间 O(1)
- 不同存储方案可能产生不同的常数因子(如缓存命中率差异),但时间复杂度本身不会改变
因此,无论采用行优先还是列优先存储,其时间复杂度仍为标准的矩阵乘法复杂度 $O(n^3)$。
15
考虑以下 C 函数。
int fun1(int n)
{
int i, j, k, p, q = 0;
for (i = 1; i < n; ++i)
{
p = 0;
for (j=n; j > 1; j=j/2)
++p;
for (k=1; k < p; k=k*2)
++q;
}
return q;
}
下列哪一项是函数 fun1
的时间复杂度?( )
时间复杂度分析:
- 外层循环
for (i = 1; i < n; ++i)
运行次数为 Θ(n) - 中间循环
for (j=n; j > 1; j=j/2)
每次将 j 折半,运行次数为 Θ(log n),因此变量 p 的值为 log n - 内层循环
for (k=1; k < p; k=k*2)
以指数方式增长,运行次数为 Θ(log p) = Θ(log log n)
- 外层循环
总时间复杂度计算: $$ T(n) = n \times (\log n + \log \log n) $$ 其中 $\log n$ 是主导项,因此最终简化为: $$ T(n) = n \log \log n $$
16
一个无序列表包含 n 个不同的元素。在该列表中找到一个既不是最大值也不是最小值的元素所需的比较次数是( ):
我们只需要考虑任意三个元素并进行比较。因此,比较次数是常数,时间复杂度为 Θ(1)。
关键点:
- 需要返回任意一个既不是最大值也不是最小值的元素
- 例如数组
{10, 20, 15, 7, 90}
,输出可以是10
、15
或20
- 从给定列表中任取三个元素(如
10
、20
和7
),通过 3 次比较 即可确定中间元素为10
17
假设我们想将存储在数组中的 i 个数重新排列,使得所有负值都出现在正值之前。在最坏情况下所需的最少交换次数是( ):
解析:
- 当数组中存储了
i
个数时,最坏情况发生在正负数分布最为分散的情形(如正负交替排列) - 此时需要将所有正数与负数进行交换操作
- 最坏情况下正数的数量为
i/2
- 由于每次交换操作最多能将两个元素归位(一个正数到右侧,一个负数到左侧),因此理论最小交换次数应为
floor(i/4)
级别 - 题目中所有选项均未覆盖这一复杂度范围,故选择 “以上都不对”
18
将下列函数按渐近增长顺序从小到大排列:
A. $n^{1/3}$
B. $e^n$
C. $n^{7/4}$
D. $n logn$
E. $1.0000001^n$
B 和 E 属于指数函数,因此 {B,E} > > > {A, C, D}。
B > > > E。
A < < < {C, D}。
D < < < C
因此正确顺序为 A < < < D < < < C < < < E < < < B。
19
合并五个文件(A 有 10 条记录,B 有 20 条记录,C 有 15 条记录,D 有 5 条记录,E 有 25 条记录)所需的最小记录移动次数是:( )
解析:
- 使用最优归并模式算法,按记录数从小到大排列文件:D → A → C → B → E
- 初始记录数序列:5, 10, 15, 20, 25
- 合并过程及记录移动次数计算:
- 第一次合并 D(5) + A(10) → 新文件 F(15),移动次数 5+10=15
- 第二次合并 F(15) + C(15) → 新文件 G(30),移动次数 15+15=30
- 第三次合并 G(30) + B(20) → 新文件 H(50),移动次数 30+20=45
- 第四次合并 H(50) + E(25) → 最终文件 I(75),移动次数 50+25=75
- 总记录移动次数 = 15 + 30 + 45 + 75 = 165
因此,选项 (A) 正确。
20
考虑以下 C 函数定义:
int Trial(int a, int b, int c)
{
if ((a >= b) && (c < b)) return b;
else if (a >= b) return Trial(a, c, b);
else return Trial(b, a, c);
}
该函数 Trial
的作用是( ):
- 函数行为:
Trial(a,b,c)
实际上通过递归调用实现了对三个参数的排序逻辑,最终返回的是三者的中位数(即第二大的元素)。 - 特殊场景:当
a = b = c
时,函数会陷入无限递归循环,导致程序崩溃或栈溢出。
因此,选项 (D) 正确。
21
给定一个大小为 n 的数组 A,其由一个递增序列后紧接一个递减序列组成。使用最优算法判断给定数字 x 是否存在于该数组中,( )?
解析:
- 该问题可通过二分查找法解决
- 最坏情况下时间复杂度为 Θ(log n)
- 因此选项 (A) 正确
22
考虑递归方程
$$ T(n) = \begin{cases} 1, & \text{当 } n = 0 \\ 2T(n-1), & \text{当 } n > 0 \end{cases} $$
则 T(n) 的大 O 阶是( )
解析:
通过递归展开可得:
$$ T(n) = 2T(n-1) = 2[2T(n-2)] = 2^2T(n-2) $$
$$ = 2^2[2T(n-3)] = 2^3T(n-3) $$
$$ \cdots $$
$$ = 2^kT(n-k) $$
当 $ n-k=0 $ 即 $ k=n $ 时,若 $ T(0)=1 $,则:
$$ T(n) = 2^n \cdot T(0) = 2^n $$
因此时间复杂度为 $ O(2^n) $。
23
考虑以下程序:
void function(int n) {
int i, j, count = 0;
for (i = n / 2; i <= n; i++)
for (j = 1; j <= n; j = j * 2)
count++;
}
该程序的时间复杂度是( )
- 外层循环执行 $ \frac{n}{2} $ 次
- 内层循环执行 $ \log n $ 次
因此程序的总时间复杂度为 O(n log n),对应选项 (D)
24
如果 $b$ 是分支因子,$m$ 是搜索树的最大深度,那么贪婪搜索的空间复杂度是多少?( )
- 在二叉树中,当分支因子为 2 且高度为 n 时,空间复杂度为 O(2n)
- 在三叉树中,当分支因子为 3 且高度为 n 时,空间复杂度为 O(3n)
- 因此,若搜索树的分支因子为 b 且最大深度为 m,则贪婪搜索的空间复杂度为 O(bm)
综上所述,选项 (C) 正确。
25
一个递归函数 $ h $ 定义如下:
$$ h(m) = \begin{cases} k, & \text{当 } m = 0 \\ 1, & \text{当 } m = 1 \\ 2h(m-1) + 4h(m-2), & \text{当 } m \geq 2 \end{cases} $$
若 $ h(4) = 88 $,则 $ k $ 的值是( )。
根据题目给出的条件:$ h(4) = 88 $
$$ \begin{align*} 88 &= 2h(3) + 4h(2) \ &= 2[2h(2) + 4h(1)] + 4h(2) \ &= 8h(2) + 8h(1) \ &= 8(2 + 4k) + 8 \ &= 24 + 32k \end{align*} $$
解得 $ k = 2 $。因此选项 (C) 正确。
26
下面 C 函数的时间复杂度是(假设 $n>0$)( )
int recursive(int n) {
if(n == 1)
return (1);
else
return (recursive(n-1) + recursive(n-1));
}
代码的递归关系式为 T(n) = 2T(n-1) + k。我们可以通过代入法求解该递归式:
初始递归式:
$$ T(n) = 2T(n-1) + k $$
代入展开:
$$ \begin{align*} &= 2(2T(n-2) + k) + k \\ &= 2(2(2T(n-3) + k) + k) + k \\ &\vdots \\ &= 2^x \cdot T(n-x) + k(2^x - 1) \end{align*} $$
当满足基本情况时(n=1),令 x = n-1: $$ \begin{align*} T(n) &= 2^{n-1} \cdot T(1) + k(2^{n} - 1) \\ &= O(2^n) \end{align*} $$
因此,选项 (D) 正确。
27
确定算法效率时,时间因素是通过以下哪种方式衡量的?( )
确定算法效率时,时间因素是通过计算关键操作的数量来衡量的。
错误选项解析:
- 语句数量:不能通过计算语句数量来衡量,因为语句数量少并不意味着程序更高效。很多时候我们需要编写更多代码以提高效率。
- 空间复杂度:空间复杂度是以千字节为单位进行衡量的,与时间因素无关。
因此,选项 (B) 是正确的。
28
设 $T(n)$ 由 $T(1) = 10$ 和 $T(n+1) = 2n + T(n)$ 定义,其中 $n ≥ 1$ 为整数。以下哪项表示 $T(n)$ 作为函数的增长阶数?( )
T(n+1) = 2n + T(n)
通过代入法:
T(n+1) = 2n + (2(n-1) + T(n-1))
T(n+1) = 2n + (2(n-1) + (2(n-2) + T(n-2)))
T(n+1) = 2n + (2(n-1) + (2(n-2) + (2(n-3) + T(n-3))))
T(n+1) = 2n + 2(n-1) + 2(n-2) + 2(n-3)……2(n-(n-1) + T(1))
T(n+1) = 2[n + n + n + …] - 2[1 + 2 + 3 +…]
T(n+1) = 2[n·n] - 2[n(n+1)/2]
T(n+1) = 2[n²] - [n² + n]
T(n+1) = n² - n
T(n+1) = O(n²)
29
当以下递归函数被调用时,n=5 的输出是什么?( )
int foo(int n) {
if (n == 0)
return 1;
else
return n * foo(n - 1);
}
给定的递归函数计算非负整数 n 的阶乘。当函数以 n=5 调用时,函数会检查 n 是否等于 0。由于 n 不等于零,它返回 n 乘以将 n-1 作为输入调用相同函数的值。递归调用会持续进行直到达到基准情况(n==0)。foo(5) 的计算过程如下:
foo(5) = 5 * foo(4)
foo(4) = 4 * foo(3)
foo(3) = 3 * foo(2)
foo(2) = 2 * foo(1)
foo(1) = 1 * foo(0)
foo(0) = 1
逐步代入计算:
将
foo(0)=1
代入foo(1)
:foo(1) = 1 * 1 = 1
将
foo(1)=1
代入foo(2)
:foo(2) = 2 * 1 = 2
将
foo(2)=2
代入foo(3)
:foo(3) = 3 * 2 = 6
将
foo(3)=6
代入foo(4)
:foo(4) = 4 * 6 = 24
将
foo(4)=24
代入foo(5)
:foo(5) = 5 * 24 = 120
因此,当以 n=5 调用该函数时,输出为 120,即选项 C。
2 - B 树
1
考虑一个 B+ 树,其中每个节点的最大键数为 5。任何非根节点中最小的键数是多少?( )
解释:
- 最大子节点数:每个节点最多有 $5 + 1 = 6$ 个子节点(键数+1)。
- B+ 树性质:非根节点的最小子节点数为 $\left\lceil \frac{6}{2} \right\rceil = 3$。
- 最小键数计算:子节点数减 1 即为键数,因此最小键数为 $3 - 1 = 2$。
2
数据库中更倾向于使用 B+ 树而不是二叉树,因为( )
解释:
- 磁盘访问速度较慢,因此减少 I/O 操作次数至关重要
- B+ 树通过高扇出度(通常 100 或更多)显著降低树高度
- 相比二叉查找树,B+ 树能以更少层级完成查找
- 高扇出特性使每次磁盘访问能检索更多子节点信息
3
从空的 4 阶 B 树开始,通过连续插入 10 个元素,最多可能发生多少次节点分裂操作?( )
插入第 3 个键
初始状态:10 20 30
插入第 4 个键(第一次分裂)
插入40
后触发分裂:30 / \ 10*20 40
插入第 5 个键(无分裂)
插入5
至左子节点:30 / \ 5*10*20 40
插入第 6 个键(第二次分裂)
插入8
触发分裂:8*30 / | \ 5 10*20 40
插入第 7 个键(无分裂)
插入15
至右子节点:8*30 / | \ 5 10*15*20 40
插入第 8 个键(第三次分裂)
插入12
触发分裂:8*12*30 / / \ \ 5 10 15*20 40
插入第 9 个键(无分裂)
插入17
至右子节点:8*12*30 / / \ \ 5 10 15*17*20 40
插入第 10 个键(第四次和第五次分裂)
插入13
导致两次分裂:12 / \ 8 15*30 / \ / | \ 5 10 13 17*20 40
结论:最大分裂次数为 5 次。
关键点:
- 4 阶 B 树节点最多容纳 3 个键,满载后插入新键会触发分裂
- 通过优先向有空间的节点插入,可最大化分裂次数
- 最终分裂路径包含 5 次节点分裂操作
4
B+ 树中叶节点的阶数是指其最多能容纳的 (value, 数据记录指针) 对的数量。已知磁盘块大小为 1K 字节,数据记录指针长度为 7 字节,值域长度为 9 字节,磁盘块指针长度为 6 字节,问该叶节点的阶数是多少?
解析:
- 磁盘块大小 = 1024 字节
- 数据记录指针大小 $ r = 7 $ 字节
- 值域大小 $ V = 9 $ 字节
- 磁盘块指针 $ p = 6 $ 字节
设叶节点的阶数为 $ m $。B+ 树的叶节点最多包含 $ m $ 个记录指针、$ m $ 个值和一个磁盘块指针。
根据空间约束条件:
$$
r \cdot m + V \cdot m + p \leq 1024
$$
$$
(7 + 9)m + 6 \leq 1024
$$
$$
16m + 6 \leq 1024
$$
$$
16m \leq 1018
$$
$$
m \leq 63.625
$$
因此,最大整数 $ m = 63 $,对应选项 A。
5
要在关系 STUDENT 的 Name 属性上建立一个 B+ 树索引。假设所有学生姓名长度为 8 字节,磁盘块大小为 512 字节,索引指针大小为 4 字节。在这种情况下,B+ 树的最佳阶数(即每个节点的指针数量)应选择什么?
- 单条记录大小:$8\ \text{字节} + 4\ \text{字节} = 12\ \text{字节}$
- 设阶数为 $N$
- 每个磁盘块存储的索引项数量满足:
$$ (N - 1) \times 12\ \text{字节} + 4\ \text{字节} \leq 512\ \text{字节} $$ - 化简方程:
$$ 12N - 8 = 512 \implies 12N = 520 \implies N = \frac{520}{12} \approx 43.33 $$ - 取整数部分作为最大可行解,故最佳阶数为 43
6
考虑一个 B+ 树,其中搜索键长度为 12 字节,块大小为 1024 字节,记录指针长度为 10 字节,块指针长度为 8 字节。该树中每个非叶节点最多可容纳的键数量是( ):
设 B+ 树的阶数为 $ m $
非叶节点存储结构需满足: $$ m \times 8 + (m - 1) \times 12 \leq 1024 $$
推导过程:
- 块指针总占用:$ m \times 8 $
- 搜索键总占用:$ (m - 1) \times 12 $
- 总占用不超过块大小 1024 字节
解得: $$ m \leq 51 $$
由于非叶节点的键数量为 $ m - 1 $,故最大键数量为 50。
7
用于大型数据库表索引的 B 树共有四层(包括根节点)。如果在此索引中插入一个新键,则在此过程中最多可能创建的新节点数为( ):
- 节点特性:一个节点的子节点数量等于其包含的键数加 1。
- 树高变化:给定的树有 4 层,若插入一个新键导致树的高度增加一层,则在此过程中最多可能创建的新节点数为 5 个。
当插入新键导致 B 树高度增加时,分裂会从叶子节点开始,逐层向上进行。对于 4 层的 B 树:
- 叶子节点分裂 → 新增 1 个节点;
- 第二层节点分裂 → 新增 1 个节点;
- 第三层节点分裂 → 新增 1 个节点;
- 根节点分裂 → 新增 2 个节点(原根节点拆分为两个子节点,新增一个根节点)。
总计新增 1 + 1 + 1 + 2 = 5 个新节点。
8
以下关于 B/B+ 树的说法中,哪一项是错误的?( )
两者的渐进时间复杂度都是 O(log n) 级别。
- 理论分析:B/B+ 树与红黑树的查找时间复杂度均为 O(log n),但 B/B+ 树的底数更大(取决于节点容量),因此实际层级更少。
- 实际表现:由于 B/B+ 树更适合磁盘 I/O 操作,其常数因子更优,但在内存数据结构中,红黑树的实现效率可能更高。
- 结论:选项 B 的表述存在误导性,因为“通常优于”忽略了具体应用场景的差异。
9
在 B+ 树索引中,内部节点的阶数是指其最多可以拥有的子节点数量。已知每个子指针占用 6 字节,搜索字段值占用 14 字节,块大小为 512 字节。问:该内部节点的阶数是多少?( )
键值大小 = 14 字节(已知)
子指针 = 6 字节(已知)
计算公式推导
B+ 树内部节点存储结构满足:
$$
\text{块大小} \geq (n - 1) \times \text{键值大小} + n \times \text{子指针大小}
$$
代入已知数值:
$$
512 \geq (n - 1) \times 14 + n \times 6
$$
展开并化简:
$$
512 \geq 14n - 14 + 6n \quad \Rightarrow \quad 512 \geq 20n - 14
$$
$$
20n \leq 526 \quad \Rightarrow \quad n \leq \frac{526}{20} = 26.3
$$
结论
由于阶数 $n$ 必须为整数,向下取整后 $n = 26$。因此选项 (C) 正确。
注:B+ 树内部节点的阶数定义为最大子节点数,而非键值对数量。本题通过容量约束反推最大可能的 $n$ 值。
10
用于大型数据库表索引的 B 树包含根节点在内共有四个层级。若在此索引中插入一个新的键,则在此过程中最多可能新创建的节点数为( )
解析:
- 当 B 树需要分裂时,每个层级都可能产生一个新节点。
- 由于树有四层(根节点到叶节点),最坏情况下每层都会发生一次分裂,因此最多会创建 4 个新节点。
- 但根节点分裂时会产生两个子节点,因此总共有 4 + 1 = 5 个新节点。
11
在一个包含 100 万条记录的文件中,若使用 B+ 树索引且树的阶数为 100,则最多需要访问多少个节点?( )
我们需要计算 B+ 树中最多需要访问的节点数,因此需考虑最小填充因子。已知记录数=100 万=10⁶(给定),B+ 树的阶数=每个节点的指针数=p=100(给定)。
计算步骤:
- 最小指针数:每个节点的最小指针数=⌈p/2⌉=⌈100/2⌉=50
- 最后一层节点数:10⁶ / 50 = 2×10⁴
- 倒数第二层节点数:2×10⁴ / 50 = 400
- 倒数第三层节点数:400 / 50 = 8
- 倒数第四层节点数:8 / 50 = 1
结论:
B+ 树的层级数=4,因此最多需要访问 4 个节点。选项 (B) 正确。
12
B+ 树被认为是平衡的,因为( )
在 B 树和 B+ 树中,所有叶节点的深度(根节点到叶节点的路径长度)是相同的。插入和删除操作会确保这一点。具体特性包括:
- 严格平衡机制:通过插入时从根节点增加高度(而非像 BST 从叶节点增加),删除时将根节点下移一级(不同于 BST 从底部收缩),从而保证所有叶节点深度一致。
- 对比 BST 的差异:
- BST 插入/删除可能导致树高变化集中在叶节点
- B+ 树通过自顶向下的高度调整维持全局平衡
- 实现保障:这种设计使得查询性能具有确定性,所有查找路径长度相同,是数据库索引选择 B+ 树的核心原因。
13
以下哪项是正确的?( )
逐一分析各个选项:
- 选项 A 错误:B 树和 B+ 树都用于磁盘存储数据。
- 选项 B 正确:B+ 树通过叶节点间的链式连接实现范围查询的线性遍历,而 B 树需遍历整棵树。数据库常用 B+ 树索引,因其叶节点链表特性显著提升范围查询效率。
- 选项 C 错误:B 树与 B+ 树均可用于主索引和次级索引,二者并无此限制。
- 选项 D 错误:树的高度由记录数量及节点最大键值数(树的阶数)共同决定。
3 - 数组
1
在包含 n 个整数的已排序数组中,确定某个整数是否出现超过 n/2 次所需的最小比较次数是( ):
如果选择Θ(1),请考虑示例:
- {1, 2, 2, 2, 4, 4}
- {1, 1, 2, 2, 2, 2, 3, 3}
判断已排序(升序)数组中是否存在出现次数超过 n/2 次的整数的最佳方法是使用二分查找。具体步骤如下:
- 通过分治技术在 O(log(n)) 时间内找到目标元素的首次出现位置
i
- 通过分治技术在 O(log(n)) 时间内找到目标元素的末次出现位置
j
- 计算该元素的出现次数:
count = j - i + 1
时间复杂度分析:
O(log n) [首次查找]
+ O(log n) [末次查找]
+ 1 [计数计算]
= O(log n)
2
一个算法对一组键值来自线性有序集合的数据项执行 $(logN)^{1/2}$ 次查找操作、$N$ 次插入操作、$(logN)^{1/2}$ 次删除操作和 $(logN)^{1/2}$ 次减键操作。对于删除操作,会提供指向必须删除记录的指针;对于减键操作,也会提供指向其键值被减少的记录的指针。如果目标是通过考虑所有操作实现最佳的总渐近时间复杂度,那么以下哪种数据结构最适合该算法使用?( )
- 无序数组:插入操作的时间复杂度为 O(1),因为可以在末尾直接插入。
- 最小堆:插入操作需要 O(log n) 时间(参考二叉堆操作)。
- 有序数组:插入操作可能需要移动所有元素,最坏情况下为 O(n)。
- 有序双向链表:需要 O(n) 时间来找到插入位置。
由于插入操作的数量在渐近意义上最高(N 次),因此优先选择插入效率最高的 无序数组。尽管其他操作如删除和减键可能需要额外开销,但题目明确提供了直接指针,使得这些操作可在 O(1) 时间内完成,从而整体时间复杂度由插入操作主导。
3
考虑一个包含正数和负数的数组。将相同符号的数字分组(即所有正数在一边,所有负数在另一边)的算法最坏情况下的时间复杂度是多少?( )
解释:
- 可以采用快速排序的分区思想进行分组
- 通过单次遍历即可完成符号分组操作
- 时间复杂度与数组长度呈线性关系
- 最终时间复杂度为 O(N)
4
设 A[1...n]
是一个包含 n
个互不相同数字的数组。若 i < j
且 A[i] > A[j]
,则称 (i, j)
为 A
的一个逆序对。在任意 n
个元素的排列中,逆序对的期望数量是多少?( )
- 数组中共有 $ \frac{n(n-1)}{2} $ 个满足 $ i < j $ 的数对组合
- 对于任意一对 $ (a_i, a_j) $,由于元素互异且随机排列,出现逆序对的概率为 $ \frac{1}{2} $
- 根据期望的线性性质,总期望为: $$ \frac{1}{2} \times \frac{n(n-1)}{2} = \frac{n(n-1)}{4} $$
5
考虑一个二维数组 A[20][10]
。假设每个内存单元存储 4
个字,数组 A
的基地址为 100
,元素按行优先顺序存储且第一个元素是 A[0][0]
。A[11][5]
的地址是多少?( )
计算行偏移:
$$ \text{行偏移} = 11 \times 10 \times 4 = 440 $$
因此,A[11][0] 的地址为:
$$ 100 + 440 = 540 $$
计算列偏移: $$ \text{列偏移} = 5 \times 4 = 20 $$ 最终地址为: $$ 540 + 20 = 560 $$
6
数组 A
包含 n
个整数,位于 A[0], A[1]...A[n-1]
。要求将数组元素循环左移 k
位,其中 1 <= k <= (n-1)
。给出一个不完整的算法,在不使用额外数组的情况下以线性时间完成此操作。请通过填充空白处来完善该算法。假设所有变量均已正确声明。
min = n;
i = 0;
while(___________){
temp = A[i];
j = i;
while(________){
A[j] = ________;
j = (j + k) % n;
If(j < min) then min = j;
}
A[(n + i - k) % n] = _________;
i = ___________;
}
在题目给出的五个空白中,最后两个空白必须是
temp
和 i+1
,因为第四、第五个空白的所有选项都包含这两个值。现在考虑第一个空白,当初始时 i=0
且 min=n
时,若条件为 i>min
则会直接退出 while
循环,因此第一个空白应为 i < min
,这意味着选项 (B) 或 (D) 可能正确。假设选项 (B) 正确,则第二个 while
循环的条件为 j!=(n+i) mod n
。由于 (n+i) mod n=i
且代码第 3 行将 i
赋值给 j
,此时 j
始终等于 i
,导致控制流永远不会进入内部循环,而实际上需要进入内部循环才能实现左移 k
位的操作。因此选项 (D) 正确。7
以下哪项正确声明了一个数组?( )
解析:
选项 A 正确地声明了一个整数数组 geeks
,其大小为 20。
int
指定了数组元素的数据类型为整型。geeks
是数组的名称。[20]
表示数组可存储 20 个元素。
其他选项均不符合 C/C++ 的数组声明语法规范。
8
在 C++ 中,一个三维数组声明为 int A[x][y][z]
。假设数组元素按行优先顺序存储且索引从 0 开始。那么,位置 A[p][q][r]
的地址可以计算如下(其中 w 是整数的字长):( )
解析:
行优先存储原理
三维数组int A[x][y][z]
在内存中按行优先顺序存储,其访问顺序遵循:- 第一维(x)变化最慢
- 第二维(y)次之
- 第三维(z)变化最快
地址计算步骤
- 定位到第 p 行
需跨越前 p 行的所有元素: $$ \text{元素数量} = p \times (y \times z) $$ - 定位到第 q 列
需跨越当前行中前 q 列的所有元素: $$ \text{元素数量} = q \times z $$ - 定位到第 r 个元素
直接取当前列的第 r 个元素: $$ \text{元素数量} = r $$
- 定位到第 p 行
总偏移量与地址公式
- 总元素偏移量: $$ \text{Total Offset} = y \cdot z \cdot p + z \cdot q + r $$
- 最终地址: $$ \text{Address} = &A[0][0][0] + w \cdot (\text{Total Offset}) $$ 即: $$ &A[0][0][0] + w \cdot (y \cdot z \cdot p + z \cdot q + r) $$
结论
选项 B 的表达式完全匹配上述推导结果,因此 选项 B 正确。
9
设 A 是一个 n×n 的方阵。考虑以下程序。预期输出是什么?
C = 100
for i = 1 to n do
for j = 1 to n do
{
Temp = A[i][j] + C
A[i][j] = A[j][i]
A[j][i] = Temp - C
}
for i = 1 to n do
for j = 1 to n do
Output(A[i][j])
- 关键分析:观察第一个循环中的三步操作:
Temp = A[i][j] + C
A[i][j] = A[j][i]
A[j][i] = Temp - C
- 执行效果:当
i ≠ j
时,该操作实现了A[i][j]
与A[j][i]
的值交换(通过中间变量Temp
)。 - 双重交换机制:由于循环遍历所有
(i, j)
组合,每个非对角线元素会被交换两次(一次作为(i,j)
,另一次作为(j,i)
),从而恢复原始值。 - 结论:矩阵整体未发生任何变化,最终输出仍为原矩阵 A。
10
程序 P 读取 500 个范围在 [0..100] 的整数,这些整数代表 500 名学生的成绩。然后程序输出每个高于 50 的成绩的出现频率。对于 P 来说,存储这些频率的最佳方式是什么?( )
解析
- 题目要求统计高于 50 的成绩的频率
- 成绩范围是 [0..100],因此高于 50 的有效成绩范围是 51 到 100(共 50 个不同值)
- 每个值对应的频率可以用一个长度为 50 的数组存储
- 其他选项要么空间浪费(如 100/500/550),要么不符合实际需求
- 因此,一个包含 50 个数字的数组是最优解
11
以下程序中,调用函数 fun
的正确方式是什么?( )
#include <bits/stdc++.h>
#include <iostream>
using namespace std;
void fun(char* arr) {
int i;
unsigned int n = strlen(arr);
for (i = 0; i < n; i++)
cout << " " << arr[i];
}
// 主程序
int main() {
char arr[] = {'g', 'e', 'e', 'k', 's', 'q', 'u', 'i', 'z'};
// 如何在此处调用上述函数以打印字符元素?
return 0;
}
- 数组退化规则:在 C/C++ 中,数组作为函数参数传递时会自动退化为指向其首元素的指针
- 参数匹配:
char arr[]
在函数参数中等价于char*
类型 - 正确调用:直接使用
arr
即可(等价于&arr[0]
),与函数参数类型char*
完全匹配 - 错误选项分析:
fun(&arr)
实际传递的是char(*)[9]
类型(指向数组的指针)_arr
不是合法标识符None
选项不符合实际需求
12
设 A 是一个 n x n 的矩阵。考虑以下程序。预期输出是什么?
void fun(int A[][N]) {
for (int i = 0; i < N; i++)
for (int j = i + 1; j < N; j++)
swap(A[i][j], A[j][i]);
}
- 解析:
在函数fun
中,通过双重循环遍历矩阵的上三角区域(即 $i < j$ 的位置)。- 对于每个元素 $A[i][j]$,将其与 $A[j][i]$ 交换。
- 这一操作实际上将矩阵的行和列互换位置,最终得到的是矩阵 $A$ 的转置矩阵。
因此,正确答案是 (C)。
13
以下哪项是数组的局限性?( )
假设我们声明一个数组:
arr[5] = {1, 2, 3};
此时数组的大小被声明为 5,但实际仅初始化了 3 个元素。这种预分配空间与实际使用量的差异会直接导致内存资源的浪费。因此选项(D)描述的情况是数组的典型局限性。
14
考虑以下程序,该程序基本上在做什么?
#include<bits/stdc++.h>
using namespace std;
void print(char a[], int n, int ind) {
for(int i = ind; i < n + ind; i++)
cout << a[(i % n)] << " ";
}
int main() {
char a[] = {'A','B','C','D','E','F'};
int n = sizeof(a)/sizeof(a[0]);
print(a, n, 3);
return 0;
}
在上述程序中,我们只是将数组元素循环右移 3 位。循环从 i=3
运行到 9
,在循环内部:
a[3%6] = a[3] = 'D'
a[4%6] = a[4] = 'E'
a[5%6] = a[5] = 'F'
a[6%6] = a[0] = 'A'
a[7%6] = a[1] = 'B'
a[8%6] = a[2] = 'C'
因此选项 (B) 正确。
15
请填写空白以完成程序,实现将数组旋转 d 个元素。
/*Function to left rotate arr[] of size n by d*/
void Rotate(int arr[], int d, int n) {
int p = 1;
while(_______) {
int last = arr[0];
for(int i = 0; _______i++) {
arr[i] = arr[i+1];
}
__________
p++;
}
}
解析:
第一个空白处(while 循环条件)
p <= d
因为初始值p = 1
,需要循环执行d
次(每次左移一个元素),所以条件应为p <= d
。第二个空白处(for 循环条件)
i < n - 1
循环需将前n-1
个元素依次前移,因此终止条件为i < n - 1
。第三个空白处(更新最后一个元素)
arr[n - 1] = last;
将原首元素last
赋值给数组末尾,完成单次左移操作。
综上,选项 (A) 正确。
16
考虑以下程序。预期输出是什么?
void fun(int arr[], int start, int end) {
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
}
该函数通过交换首尾元素逐步向中间移动,最终实现数组反转
4 - 链表
1
以下函数对于给定的链表(第一个节点为 head)执行什么操作?
void fun1(struct node* head) {
if (head == NULL)
return;
fun1(head→next);
printf("%d ", head→data);
}
此函数 fun1
通过递归方式以逆序打印链表的元素。其执行逻辑如下:
- 递归终止条件:当传入的
head
为NULL
时,函数直接返回,表示已到达链表末尾。 - 递归调用:函数优先调用自身处理下一个节点(
head→next
),这一过程会持续到链表最后一个节点。 - 回溯打印:当递归调用栈开始回退时,依次从最后一个节点向第一个节点打印每个节点的值(
printf("%d ", head→data);
)。
因此,该函数通过先递归遍历至链表末尾,再在回溯阶段按逆序访问节点的方式,实现链表的逆序输出。
2
当将链表数据结构与数组进行比较时,以下哪项或多项描述是正确的?( )
当比较链表数据结构与数组时,以下几点成立:
插入和删除操作
链表允许在列表的任何位置高效地进行插入和删除操作,因为只需调整指针;而在数组中,这些操作可能代价较高,因为插入或删除点之后的所有元素都需要移动。内存分配
链表使用动态内存分配,因此可以根据需要增长或缩小;而数组具有固定大小,需要提前分配连续的内存块。访问时间
数组提供对任意元素的常数时间访问(假设已知索引),而链表访问元素的时间与列表中的元素数量成线性关系,因为需要从开头遍历列表才能找到目标元素。随机访问
数组支持随机访问,即可以通过索引直接访问任意元素;而链表不支持随机访问,需要遍历列表才能找到特定元素。
3
考虑以下函数,该函数以双链表头节点的引用作为参数。假设双链表节点包含前驱指针 prev
和后继指针 next
。
void fun(struct node** head_ref) {
struct node* temp = NULL;
struct node* current = *head_ref;
while (current != NULL) {
temp = current→prev;
current→prev = current→next;
current→next = temp;
current = current→prev;
}
if (temp != NULL)
*head_ref = temp→prev;
}
假设将以下双链表 1 ↔ 2 ↔ 3 ↔ 4 ↔ 5 ↔ 6
的头节点引用传递给上述函数。函数调用后,修改后的链表应是什么样子?( )
函数行为分析
函数通过迭代方式实现双链表的反转:- 从头节点开始,逐个节点交换
prev
和next
指针 - 每次迭代后,
current
移动到原本的前驱节点(此时已变为后继) - 循环终止时,
temp
指向原链表的尾节点(现为新头节点的前驱) - 最终将头指针更新为
temp→prev
(即原链表的尾节点)
- 从头节点开始,逐个节点交换
执行过程示例
原始链表:1 ⇄ 2 ⇄ 3 ⇄ 4 ⇄ 5 ⇄ 6
反转后结果:6 ⇄ 5 ⇄ 4 ⇄ 3 ⇄ 2 ⇄ 1关键细节
- 每个节点的
prev
和next
指针被直接交换 - 头指针最终指向原链表的最后一个节点
- 时间复杂度 O(n),空间复杂度 O(1)
因此选项 (C) 是正确答案。
- 每个节点的
4
以下函数 reverse()
旨在反转一个单链表。函数末尾缺少一行代码。
/* Link list node */
struct node {
int data;
struct node* next;
};
/* head_ref 是指向链表头(或起始)指针的双指针 */
static void reverse(struct node** head_ref) {
struct node* prev = NULL;
struct node* current = *head_ref;
struct node* next;
while (current != NULL) {
next = current→next;
current→next = prev;
prev = current;
current = next;
}
/*MISSING STATEMENT HERE*/
}
应添加什么语句来替代 /*MISSING STATEMENT HERE*/
,以使该函数能正确反转链表?( )
缺失的语句应该将 *head_ref(这是一个双指针)的值设置为 prev,因为 prev 是反转后的链表的新头节点。
因此正确选项是 (A)。
5
当 start 指向以下链表的第一个节点 1→2→3→4→5→6 时,以下函数的输出是什么?( )
void fun(struct node* start) {
if (start == NULL)
return;
printf("%d ", start→data);
if (start→next != NULL)
fun(start→next→next);
printf("%d ", start→data);
}
函数执行流程分析:
前序遍历阶段
- 首次访问节点时打印数据:
1 → 3 → 5
- 通过
fun(start→next→next)
跳过相邻节点,形成间隔访问
- 首次访问节点时打印数据:
回溯阶段
- 到达末尾节点5后,递归栈开始回退
- 每层递归返回时再次打印当前节点数据:
5 → 3 → 1
完整输出序列
- 前序遍历结果:
1 3 5
- 回溯遍历结果:
5 3 1
- 合并后总输出:
1 3 5 5 3 1
- 前序遍历结果:
6
以下 C 函数以一个单链列表作为输入参数。它通过将最后一个元素移动到列表的前面来修改该列表,并返回修改后的列表。代码中有一部分被留空。请选择包含正确伪代码的选项以填充空白行。
typedef struct node {
int value;
struct node *next;
} Node;
Node* move_to_front(Node* head) {
Node *p, *q;
if ((head == NULL : || (head→next == NULL)) return head;
q = NULL;
p = head;
while (p→next != NULL) {
q = p;
p = p→next;
}
_____________________________
return head;
}
要将最后一个元素移到列表的前面,需要执行以下步骤:
将倒数第二个节点设为最后一个节点
将q
的next
指针设为NULL
,这样原来的最后一个节点就变成了倒数第二个节点。设置新头节点的连接
将最后一个节点p
的next
指向原来的头节点head
,这样p
成为新的头节点后,仍然保留原有链表的其余部分。更新头节点
将head
指向p
,完成移动操作。
7
以下函数以一个整数的单链表作为参数,并重新排列列表中的元素。该函数被调用时,列表包含按顺序排列的整数 1, 2, 3, 4, 5, 6, 7。函数执行完成后,列表的内容会是什么?( )
struct Node {
int value;
struct Node* next;
};
void rearrange(struct Node* list) {
struct Node* p;
struct Node* q;
int temp;
if (list == NULL || list→next == NULL) {
return;
}
p = list;
q = list→next;
while (q != NULL) {
temp = p→value;
p→value = q→value;
q→value = temp;
p = q→next;
q = (p != NULL) ? p→next : NULL;
}
}
函数 rearrange()
的工作原理如下:
交换机制
函数通过两个指针p
和q
遍历链表,每次交换相邻节点的值。具体流程为:p
指向当前节点,q
指向下一个节点;- 交换
p→value
和q→value
; p
移动到q→next
,q
移动到p→next
(若存在)。
执行过程分析
初始链表为1 → 2 → 3 → 4 → 5 → 6 → 7
,函数操作步骤如下:- 第一次交换:
1 ↔ 2
→2 → 1 → 3 → 4 → 5 → 6 → 7
; - 第二次交换:
3 ↔ 4
→2 → 1 → 4 → 3 → 5 → 6 → 7
; - 第三次交换:
5 ↔ 6
→2 → 1 → 4 → 3 → 6 → 5 → 7
; - 循环终止条件满足后,最终结果为
2, 1, 4, 3, 6, 5, 7
。
- 第一次交换:
验证示例
若输入为3, 5, 7, 9, 11
,函数执行后输出为5, 3, 9, 7, 11
,与本题逻辑一致。
8
假设每个集合都表示为一个元素顺序任意的链表。在并集、交集、成员资格和基数这些操作中,哪一项会是最慢的?( )
基数和成员资格操作肯定不是最慢的
- 基数操作只需统计链表中的节点数量
- 成员资格操作只需遍历链表查找匹配项
交集操作
需要逐个检查 L1 的元素是否存在于 L2 中,并输出在 L2 中找到的元素并集操作
实现方式可能有多种:- 输出 L1 的所有节点,并仅输出那些不在 L2 中的节点
- 输出 L2 的节点
时间复杂度分析
上述两种操作的时间复杂度均为 O(n²),因此并集和交集是速度最慢的操作
9
考虑以下定义的函数 f
。
struct item {
int data;
struct item *next;
};
int f(struct item *p) {
return ((p == NULL) || (p→next == NULL) || ((p→data <= p→next→data) && f(p→next)));
}
对于给定的链表 p
,函数 f
返回 1 当且仅当( )。
解释:
该函数通过递归检查链表中每个节点与其后继节点的数据关系。具体逻辑如下:
基本情况:
- 如果当前节点
p
为NULL
或其后继节点为NULL
,函数返回1
。这表示链表已到达末尾,无需进一步比较。
- 如果当前节点
递归条件:
- 若当前节点的数据值小于或等于后继节点的数据值(
p→data <= p→next→data
),则继续递归检查后继节点(f(p→next)
)。 - 若上述条件不成立(即当前节点数据值大于后继节点),则整个表达式返回
0
。
- 若当前节点的数据值小于或等于后继节点的数据值(
整体判断:
- 函数仅在所有相邻节点均满足非递减顺序时,才能保证每层递归返回
1
,最终结果为1
。 - 若存在任意一对相邻节点违反非递减顺序,递归会在该层返回
0
,导致整个函数返回0
。
- 函数仅在所有相邻节点均满足非递减顺序时,才能保证每层递归返回
因此,函数 f
返回 1
的充要条件是链表中的元素严格按数据值的非递减顺序排列。
10
使用一个循环链表表示队列,仅通过一个变量 p 来访问该队列。p 应指向哪个节点,才能使入队(enQueue)和出队(deQueue)操作均能在常数时间内完成?( )
答案不是"(b) 首节点",因为从首节点无法在 O(1) 时间内获取尾节点。但如果 p 指向尾节点,则可以通过尾节点在 O(1) 时间内同时实现入队和出队操作,因为从尾节点可以立即定位到首节点。以下是示例函数(注意这些函数仅为示例且不完整,缺少基础情况处理代码)。
/* p 是指向尾节点地址的指针(双指针)。此函数在尾节点后添加新节点,并更新尾节点*p 指向新节点 */
void enQueue(struct node **p, struct node *new_node) {
/* 缺少对边界情况的处理代码,如*p 为 NULL */
new_node→next = (*p)→next;
(*p)→next = new_node;
(*p) = new_node; /* 新节点现在是尾节点 */
/* 注意:p→next 仍然是首节点,p 是尾节点 */
}
/* p 指向尾节点。此函数移除首元素并返回出队元素 */
struct node *deQueue(struct node *p) {
/* 缺少对边界情况的处理代码,如 p 为 NULL、p→next 为 NULL 等 */
struct node *temp = p→next;
p→next = p→next→next;
return temp; /* 注意:p→next 仍然是首节点,p 是尾节点 */
}
11
在单链表中查找从头开始的第 8 个元素和从尾部开始的第 8 个元素的时间复杂度分别是多少?假设链表节点数为 n,且 n > 8。
- 头部第8个元素:只需从头节点开始依次访问8次即可定位目标节点,操作次数固定为常数(与n无关),因此时间复杂度为 O(1)
- 尾部第8个元素:
- 需先遍历整个链表统计节点总数(O(n))
- 再从头节点向后移动 (n-8) 次定位目标节点(O(n))
总体时间复杂度为 O(n)
- 综合两种情况,最终答案为 A
12
是否可以仅使用每个节点一个指针来创建双向链表( )。
是的,可以通过在每个节点中存储前驱和后继节点地址的异或值来使用单个指针实现双向链表。这种技术利用了异或运算的性质:
- 异或特性:若已知当前节点地址与相邻节点地址中的任意一个,
- 计算方式:即可通过异或计算得到另一个节点的地址,
- 实现效果:从而实现双向遍历功能。
该方案通过巧妙的数据结构设计,在节省空间的同时保留了双向链表的核心操作能力。
13
给定一个单链表中某个节点 X 的指针。仅提供该节点的指针,未提供头节点的指针,能否删除该链表中的节点 X?( )
解析:
当且仅当 X 不是第一个节点时,可通过以下操作实现:
struct node *temp = X→next;
X→data = temp→data;
X→next = temp→next;
free(temp);
核心思想是通过覆盖当前节点数据并断开后续连接的方式模拟删除操作。此方法依赖于存在可被覆盖的下一个节点(即 X 不是尾节点),但题目限定条件仅为"X 不是第一个节点",因此需特别注意边界情况处理。
14
以下哪项是异或链表的应用?( )
- 异或链表是一种内存高效的链表表示方法
- 它通过存储前一个节点与后一个节点地址的异或组合实现双向遍历
- 相比传统双向链表(需存储两个指针),能显著减少内存开销
15
N 个元素存储在一个已排序的双向链表中。对于删除操作,会提供一个指向待删除记录的指针;对于减小键值操作(decrease-key),会提供一个指向执行该操作的记录的指针。某个算法按以下顺序对链表执行这些操作:Θ(N) 次删除、O(log N) 次插入、O(log N) 次查找和Θ(N) 次减小键值。所有这些操作的总时间复杂度是多少?( )
各操作时间复杂度分析
- 删除操作:需遍历链表定位目标节点,时间复杂度为 Θ(N)
- 插入/查找操作:利用有序特性通过二分查找定位位置,时间复杂度为 O(log N)
- 减小键值操作:需重新定位新键值位置并调整指针,时间复杂度为 Θ(N)
总操作次数计算
$$ M = \Theta(N) + O(\log N) + O(\log N) + \Theta(N) = O(N) $$最终时间复杂度推导
所有操作均作用于大小为 N 的链表,因此总时间复杂度为: $$ O(M \log N) = O(N \log N) $$
虽然单次删除和减小键值操作耗时较高,但其总次数与线性规模相关,整体仍满足 O(N log N) 上界。
16
要在 O(1) 时间内完成两个列表的连接操作,应使用以下哪种列表实现方式?( )
解析:
- 列表连接操作通常需要定位到列表的末尾节点
- 单链表和双链表需要从头节点开始遍历至末尾(O(n)时间复杂度)
- 循环双链表中,每个节点包含前驱和后继指针,且首尾节点相互指向
- 通过循环双链表的头节点可以直接访问尾节点(前驱指针),无需遍历
- 因此循环双链表可在常数时间内完成连接操作
17
假设存在两个单链表,它们在某一点相交后合并为一个链表。已知这两个链表的头节点,但不知道相交节点和链表长度。求从两个相交链表中找出相交节点的最优算法的最坏情况时间复杂度是多少?( )
该算法在最坏情况下具有以下特性:
- 时间复杂度:Θ(m + n)
- 空间复杂度:O(1)
实现步骤:
- 首次遍历两个链表,分别计算其长度 $ m $ 和 $ n $
- 将较长链表的起始指针向前移动 $ |m - n| $ 个节点,使两链表剩余长度对齐
- 同步移动两个链表的指针,逐节点比较地址直至找到首个相同节点
此方法通过两次线性扫描完成定位,无需额外存储空间,因此是最优解法。选项(C)正确。
18
一个队列使用非循环单链表实现,该队列具有头指针和尾指针(如图所示)。设 n 表示队列中的节点数量。若“入队”操作通过在头部插入新节点实现,“出队”操作通过删除尾部节点实现,则对于这种数据结构,以下哪项是“入队”和“出队”操作最高效的时间复杂度?
对于入队操作,其执行时间为常数时间(即Θ(1)),因为它仅修改两个指针:创建节点 P → P.Data = 数据;P.Next = Head → Head = P。
对于出队操作,需要获取单链表倒数第二个节点的地址以将其 next 指针置空。由于单链表无法访问前驱节点,必须遍历整个链表才能找到倒数第二个节点:
temp = head;
While(temp-Next-→Next != NULL){ temp = temp-Next; }
temp-→next = NULL; Tail = temp;
由于每次出队都需要遍历整个链表,因此时间复杂度为Θ(n)。选项 (B) 正确。
19
在双向链表中,插入操作会影响多少个指针?( )
解释 - 实际上,受影响的指针数量取决于插入的位置。但最多有以下三种情况:
- 插入到开头 - 影响 3 个指针
- 插入到中间 - 影响 4 个指针
- 插入到结尾 - 影响 3 个指针
20
考虑一个无序单链表的实现。假设该链表通过头指针和尾指针进行表示(即指向链表第一个节点和最后一个节点的指针)。基于这种表示方式,以下哪个操作无法在 O(1) 时间内完成?( )
- 删除链表的末节点时,需要获取倒数第二个节点的地址以将其
next
指针置为NULL
- 由于单链表无法直接访问前驱节点,必须从头节点开始遍历至倒数第二个节点
- 遍历操作的时间复杂度为 $O(n)$,因此该操作无法在常数时间内完成
21
考虑一个单链表,其中 F 和 L 分别是链表第一个和最后一个元素的指针。以下哪些操作的执行时间取决于链表的长度?( )
链表结构如下:F → 1 → 2 → 3 → L
若 F 和 L 分别为链表首尾元素的指针,则:
- 删除第一个元素:无需遍历链表,只需将
F = F→next
并释放原首节点; - 交换前两个元素:可通过临时节点直接操作,无需依赖链表长度;
- 删除最后一个元素:需要从头遍历至倒数第二个节点才能修改指针,因此时间复杂度与链表长度相关;
- 在末尾添加元素:可直接通过
L→next = 新节点
完成,无需遍历。
综上,正确选项为(C)。
22
以下链表操作步骤:
p = getnode();
info(p) = 10;
next(p) = list;
list = p;
会实现哪种类型的操作?( )
上述步骤实现了在链表头部插入新节点的操作。具体过程如下:
p = getnode()
通过getnode()
函数(假设已在别处定义)创建了一个新节点p
。info(p) = 10
将新节点p
的信息域设置为值10
。next(p) = list
将新节点p
的指针域指向当前链表的头节点(由list
指针指向)。list = p
更新list
指针使其指向新插入的节点p
,从而将其作为链表的新头节点。
这一系列操作等效于在链表的头部插入节点,也称为“压入”(push)操作。
23
在 DNA 序列比对中,哪种字符串匹配算法常用于高效识别两个 DNA 序列之间的相似性?( )
- 标准答案解析
在 DNA 序列比对领域,更常用的是 Smith-Waterman 算法 和 Needleman-Wunsch 算法 这类专门设计的动态规划算法。- Rabin-Karp 算法适用于多模式串匹配,但不适合处理 DNA 序列的局部比对需求。
- Knuth-Morris-Pratt 算法针对单模式串匹配优化,缺乏对序列相似性评估的能力。
- Z 函数主要用于字符串前缀分析,与 DNA 序列全局/局部比对目标不符。
24
以下哪种操作在双向链表中比在线性链表中执行得更高效?( )
- 双向链表的优势:当已知指向某个节点的指针时,删除该节点的操作效率更高
- 线性链表的局限性:
- 需要遍历列表以找到待删除节点及其前驱节点
- 前驱节点需更新
next
指针跳过待删除节点 - 最坏情况下时间复杂度为 O(n)(n 为链表节点数量)
- 核心差异:双向链表通过
prev
指针可直接访问前驱节点,无需额外遍历
25
在长度为 n 的链表中查找一个元素所需的时间是( )
解析
在最坏情况下,需要遍历链表中的所有元素才能完成查找操作。由于链表的存储结构决定了无法通过下标直接访问元素,因此必须从头节点开始逐个比较。
这种线性扫描的方式使得时间复杂度与链表长度成正比,即 O(n)。选项 (B) 是唯一符合这一特性的答案。
26
双链表中每个节点的最小字段数是( )
通常情况下,双链表的每个节点始终包含 3 个字段,即前驱节点指针、数据域和后继节点指针。因此答案应为选项(C)3。然而,通过使用异或(XOR)指针技术,双链表的每个节点可以仅包含 2 个字段:异或指针域和数据域。该异或指针域可同时指向前后节点,这是包含数据域的最佳情况。这种结构称为内存高效的双链表。此外,如果从异或链表中移除数据节点,则双链表的每个节点可以仅包含 1 个字段:异或指针域。但此时没有数据域,因此这种双链表在实际中并无意义。
27
一个双向链表的声明如下:
struct Node {
int Value;
struct Node* Fwd;
struct Node* Bwd;
};
其中 Fwd
和 Bwd
分别表示链表中相邻元素的前向和后向链接。假设 X
指向的既不是链表的第一个节点也不是最后一个节点,以下哪段代码可以从双向链表中删除 X
所指向的节点?( )
要从双向链表中删除一个节点,需要更新前一个节点和后一个节点的链接,使它们相互指向对方,从而将待删除的节点从链表中移除。具体来说:
- 将前驱节点的
Fwd
指针指向当前节点的后继节点(X→Bwd→Fwd = X→Fwd
) - 将后继节点的
Bwd
指针指向当前节点的前驱节点(X→Fwd→Bwd = X→Bwd
)
通过这两个操作,当前节点 X
被逻辑上从链表中移除,后续可通过内存回收机制进行物理删除。
28
考虑一个形式为 $ F \rightarrow \text{elements} \rightarrow L $ 的单链表,其中 $ F $ 是链表中第一个元素的指针,$ L $ 是链表中最后一个元素的指针。以下哪种操作的时间复杂度取决于链表的长度?( )
原因:单链表无法从尾部直接找到前一个节点,需从头遍历到倒数第二个节点,时间复杂度为 $O(n)$,取决于链表长度。其他操作均为 $O(1)$。
29
以下哪种排序算法可以在最短的时间复杂度内对随机链表进行排序?( )
- 归并排序是最适合链表的排序算法之一,时间复杂度为 $O(n \log n)$,且在链表中不依赖随机访问。
- 链表无法高效地进行下标访问,因此像快速排序和堆排序在链表上效率较低。
- 插入排序在链表上是 $O(n^2)$,不适合随机数据。
30
将 n 个元素插入到一个空的链表中,如果需要保持链表始终有序,那么最坏情况下的时间复杂度是多少?( )
- 解析:
在最坏情况下,每次插入操作都需要从链表头开始逐个比较节点,直到找到合适的位置。对于第 $ i $ 个元素($ i = 1, 2, \dots, n $),其插入所需的时间复杂度为 $ O(i) $。因此,总时间复杂度为: $$ \sum_{i=1}^{n} O(i) = O\left(\frac{n(n+1)}{2}\right) = \Theta(n^2) $$ 这种情况发生在所有元素按逆序插入时(例如升序链表中依次插入降序元素)。
31
考虑以下陈述:
i. 先进先出(FIFO)类型的操作由 STACKS 高效支持。
ii. 使用链表实现 LISTS 比使用数组实现 LISTS 在几乎所有基本操作中更高效。
iii. 使用循环数组实现 QUEUES 比使用两个索引的线性数组实现 QUEUES 更高效。
iv. 后进先出(LIFO)类型的操作由 QUEUES 高效支持。
以下哪项是正确的?( )
正确陈述是:iii. 使用循环数组实现 QUEUES 比使用两个索引的线性数组实现 QUEUES 更高效。
解释:
- i. STACKS 用于实现后进先出(LIFO)操作,而非先进先出(FIFO)操作。因此,陈述 i 错误。
- ii. 使用链表实现 LISTS 在某些操作(如在列表中间添加或删除元素)上更高效,但在其他操作(如通过索引访问元素)上效率更低。因此,该陈述错误。
- iii. 使用循环数组实现 QUEUES 比使用线性数组更高效。这是因为循环数组的 front 和 rear 指针在到达数组末尾时会绕回开头,从而更高效地执行入队和出队操作。因此,该陈述正确。
- iv. QUEUES 用于实现先进先出(FIFO)操作,而非后进先出(LIFO)操作。因此,陈述 iv 错误。
综上,选项 (C) 为正确答案。
32
考虑队列 Q1 包含四个元素,Q2 为空(如图中的初始状态所示)。允许在这两个队列上进行的操作只有 Enqueue(Q, element)
和 Dequeue(Q)
。在不使用任何额外存储的情况下,将 Q1 的元素以相反顺序放入 Q2 中所需的最少 Q1 入队操作次数是( )。
理解操作 Enq(Qx, Deq(Qy))
表示从队列 Qy 中出队一个元素并将其入队到队列 Qx。该操作不会占用额外空间,因为一个函数的返回值会立即作为另一个函数的参数传递。请注意,函数的返回值和参数存储在函数栈帧中,因此不需要额外空间,即这些数据的空间开销已包含在函数调用/操作本身中。
解题步骤
按照以下步骤解决此问题:
步骤 | 操作 | 队列状态(操作后) |
---|---|---|
0 | - | 初始状态 |
1 | Enq(Q2, Deq(Q1)) | Q1: [B, C, D]; Q2: [A] |
2 | Enq(Q2, Deq(Q1)) | Q1: [C, D]; Q2: [A, B] |
3 | Enq(Q2, Deq(Q2)) | Q1: [C, D]; Q2: [B, A] |
4 | Enq(Q2, Deq(Q1)) | Q1: [D]; Q2: [B, A, C] |
5 | Enq(Q2, Deq(Q2)) | Q1: [D]; Q2: [A, C, B] |
6 | Enq(Q2, Deq(Q2)) | Q1: [D]; Q2: [C, B, A] |
7 | Enq(Q2, Deq(Q1)) | Q1: []; Q2: [C, B, A, D] |
8 | Enq(Q2, Deq(Q2)) | Q1: []; Q2: [B, A, D, C] |
9 | Enq(Q2, Deq(Q2)) | Q1: []; Q2: [A, D, C, B] |
10 | Enq(Q2, Deq(Q2)) | Q1: []; Q2: [D, C, B, A] |
最终状态表明,无需对 Q1 执行任何入队操作即可完成目标。因此,答案为 0。
5 - 队列
1
以下是一个函数的伪代码,该函数以队列(Queue)作为参数,并使用栈 S 进行处理。
void fun(Queue* Q) {
Stack S; // 假设创建一个空栈 S
// 当 Q 不为空时循环
while (!isEmpty(Q)) {
// 从 Q 中出队一个元素并将其压入栈 S
push(&S, deQueue(Q));
}
// 当栈 S 不为空时循环
while (!isEmpty(&S)) {
// 弹出栈 S 的一个元素并将其重新入队到 Q
enQueue(Q, pop(&S));
}
}
上述函数总体上执行什么操作?( )
解析:
- 函数首先将队列
Q
中的所有元素依次出队并压入栈S
- 随后将栈
S
中的所有元素弹出并重新入队到Q
- 由于栈遵循 后进先出(LIFO) 的特性,最终队列
Q
中的元素顺序会被完全反转 - 因此选项 (D) 是正确答案
2
需要多少个栈来实现一个队列?假设你无法使用数组、链表等其他数据结构。( )
解析:
- 使用两个栈可以模拟队列的基本操作(入队和出队)
- 实现原理:
- 入队时将元素压入第一个栈
- 出队时若第二个栈为空,则将第一个栈的所有元素依次弹出并压入第二个栈,此时最先进入的元素位于栈顶
- 直接从第二个栈弹出元素即可完成先进先出的队列操作
- 因此选项 (B) 为正确答案
3
以下哪种数据结构可以高效实现优先队列?( )
假设插入操作、查看当前最高优先级元素的 peek 操作以及移除最高优先级元素的提取操作的数量大致相同。
解析
- 优先队列可以通过 二叉堆 或 斐波那契堆 等数据结构高效实现
- 二叉堆 是一种完全二叉树,满足堆属性(父节点值始终大于/小于子节点)
- 当插入与 peek/提取操作数量相近时,二叉堆能提供以下性能优势:
- 插入操作时间复杂度:O(log n)
- 查找最大/最小值(peek)时间复杂度:O(1)
- 提取最大/最小值时间复杂度:O(log n)
- 斐波那契堆在某些场景下可进一步优化摊还时间复杂度
因此选项 (C) 是正确答案。
4
使用两个栈 S1 和 S2 实现队列 Q 的代码如下:
Function Insert(Q, x):
Push x onto stack S1
Function Delete(Q):
If stack S2 is empty:
If stack S1 is also empty:
Print "Q is empty"
Return
Else:
While stack S1 is not empty:
Pop element from stack S1 and store it in x
Push x onto stack S2
Pop element from stack S2 and store it in x
Return x // Or process the popped element (if needed)
对空队列 Q 执行 n 次插入操作和 m(m ≤ n)次删除操作,且操作顺序任意。设整个过程中执行的压入操作次数为 x,弹出操作次数为 y。以下哪一项对所有 m 和 n 都成立?( )
关键分析:
- 操作顺序影响性能:插入与删除的交错方式决定操作次数范围
- 最佳情况(交替执行):
- 每次删除需 2 次弹出 + 1 次压入
- 总压入次数 = n(插入) + m(删除) = n + m
- 总弹出次数 = 2m
- 最坏情况(先全插入后全删除):
- 第一次删除需 n+1 次弹出 + n 次压入
- 后续 m-1 次删除各需 1 次弹出
- 总压入次数 = n(插入) + n(删除) = 2n
- 总弹出次数 = n+1 + (m-1) = n + m
- 结论:x 的取值范围 [n+m, 2n),y 的取值范围 [2m, n+m]
5
考虑以下操作以及队列的入队(Enqueue)和出队(Dequeue)操作,其中 $ k $ 是一个全局参数:
MultiDequeue(Q){
m = k
while (Q is not empty and m > 0) {
Dequeue(Q)
m = m - 1
}
}
在一个初始为空的队列上执行 $ n $ 次 MultiDequeue()
操作的最坏时间复杂度是多少?
解释:
- 队列初始为空,因此
while
循环的条件Q is not empty
永远不成立 - 每次调用
MultiDequeue()
实际仅执行一次赋值操作m = k
即退出循环 - 每个操作的时间复杂度为常数 $ O(1) $
- 执行 $ n $ 次操作的总时间复杂度为 $ \Theta(n) $
6
考虑以下伪代码。假设 IntQueue
是整型队列,函数 fun
的作用是什么?( )
fun(int n)
{
IntQueue q = new IntQueue();
q.enqueue(0);
q.enqueue(1);
for (int i = 0; i < n; i++)
{
int a = q.dequeue();
int b = q.dequeue();
q.enqueue(b);
q.enqueue(a + b);
print(a);
}
}
解析:
- 初始时队列包含
[0, 1]
,这是斐波那契数列的前两项 - 每次循环操作流程:
- 依次出队
a
和b
(当前队列前两个元素) - 打印
a
(保证按顺序输出斐波那契数) - 重新入队
b
和a + b
(维持队列中始终保存最近两个斐波那契数)
- 依次出队
- 通过这种队列操作机制,实现了斐波那契数列的递推生成
- 最终输出结果严格对应前
n
个斐波那契数
7
在队列(queue)数据结构中,以下哪项不是常见的操作?( )
解析:
队列是一种先进先出(FIFO)的数据结构,其核心操作包括:Enqueue
(入队):将元素添加到队尾Dequeue
(出队):移除并返回队首元素Peek
(查看):获取队首元素但不移除它
而
Shuffle
(洗牌)操作会随机重排元素顺序,这与队列严格遵循的FIFO原则相违背。因此洗牌操作不属于队列的标准操作范畴,选项 (D) 为正确答案。
8
假设一个栈的实现支持额外的 REVERSE 指令(用于反转栈内元素顺序),除了基本的 PUSH 和 POP 操作。关于这种修改后的栈,以下哪项陈述是正确的?
DEQUEUE 操作
只需执行一次 POP
操作即可完成。
ENQUEUE 操作
需通过以下三步指令序列实现:
- 执行
REVERSE
- 执行
PUSH
- 再次执行
REVERSE
因此,选项 (C) 是正确答案。
9
一个队列使用数组实现,使得 ENQUEUE 和 DEQUEUE 操作能够高效执行。以下哪项陈述是正确的(n 表示队列中的元素数量)( )?
时间复杂度分析
ENQUEUE 操作
- 尾指针在 O(1) 时间内更新
- 元素被插入到尾指针所指示的位置
- 时间复杂度:
O(1)
DEQUEUE 操作
- 头指针在 O(1) 时间内更新
- 元素从头指针所指示的位置移除
- 时间复杂度:
O(1)
最坏情况考虑
- ENQUEUE 和 DEQUEUE 操作均涉及指针更新和数组元素访问
- 这些操作均可在常数时间内完成
- 不需要
Ω(n)
或Ω(log n)
的操作 - 因此选项 (A) 是正确答案
10
设 Q 是一个包含十六个数字的队列,S 是一个空栈。Head(Q) 返回队列 Q 的头部元素但不将其从 Q 中移除。类似地,Top(S) 返回栈 S 的顶部元素但不将其从 S 中移除。考虑以下算法。该算法中 while 循环的最大可能迭代次数是( )。
最坏情况发生在队列按降序排列时。在最坏情况下,循环运行 $ n \times n $ 次。例如:
- 队列:4 3 2 1 栈:空 → 3 2 1 4
- 栈:3 2 1 4 队列:空 → 2 1 4 3
- 栈:2 1 4 3 队列:空 → 1 4 3 2
- 栈:1 4 3 2 队列:空 → 4 3 2 1
- 栈:4 3 2 1 队列:空 → 3 2 1 4
- 栈:3 2 1 4 队列:空 → 2 4 1 3
- 栈:2 4 1 3 队列:空 → 2 4 3 1
- 栈:2 4 3 1 队列:空 → 4 3 1 2
- 栈:4 3 1 2 队列:空 → 3 1 2 4
- 栈:3 1 2 4 队列:空 → 3 4 1 2
- 栈:3 4 1 2 队列:空 → 4 1 2 3
- 栈:4 1 2 3 队列:空 → 空 → 1 2 3 4
因此,选项 C 是正确答案。
11
假设你被给定一个整数队列的实现。考虑以下函数:
void f(queue Q) {
int i;
if (!isEmpty(Q)) {
i = delete(Q);
f(Q);
insert(Q, i);
}
}
上述函数 f
执行了什么操作?( )
- 关键分析:
- 函数通过递归方式逐个取出队首元素
- 每次递归调用会先处理剩余队列
- 最深层递归返回时,最先取出的元素会被最后插入
- 这种"先进后出"的特性导致整体顺序反转
- 示例演示:
假设原始队列为[1,2,3]
- 取出1 → 队列变为
[2,3]
- 取出2 → 队列变为
[3]
- 取出3 → 队列为空
- 插入3 → 队列
[3]
- 插入2 → 队列
[3,2]
- 插入1 → 队列
[3,2,1]
- 取出1 → 队列变为
- 结论:最终队列顺序与初始顺序完全相反
12
考虑一个标准的循环队列 q
实现(其队满和队空条件相同),该队列总容量为 11
,元素依次为 q[0], q[1], q[2], ..., q[10]
。
初始时,队头指针和队尾指针均指向 q[2]
。第九个元素将被添加到哪个位置?( )
- 初始状态:队头指针与队尾指针均指向
q[2]
,表示队列为空。 - 添加规则:每次添加元素时,队尾指针
(rear)
向前移动一位(按模 11 运算)。 - 第九个元素的位置:
初始位置为q[2]
,添加第一个元素后指针移至q[3]
,第二个至q[4]
,依此类推。
第九个元素对应的位置为:(2 + 9) % 11 = 11 % 11 = 0
,即q[0]
。
因此,选项 (A) 正确。
13
循环队列也称为( )。
解释:
- 循环队列是普通队列的扩展版本
- 队列的最后一个元素与第一个元素相连,形成一个环形结构
- 其操作基于 FIFO(先进先出) 原则
- 它也被称为“环形缓冲区”
- 因此选项 (A) 是正确答案
14
一个队列使用非循环单链表实现。该队列具有头指针和尾指针(如图所示)。设队列中节点数量为 $ n $。若将“入队”操作定义为在头部插入新节点,“出队”操作定义为从尾部删除节点,则此数据结构中最高效实现的“入队”和“出队”操作的时间复杂度分别为?( )
入队操作
仅需修改两个指针创建节点 P → P->Data = 数据; P->Next = Head; Head = P
因此时间复杂度为常数时间 Θ(1)。
出队操作
需要获取单链表倒数第二个节点的地址,以便将其 next 指针置空。由于单链表无法直接访问前驱节点,必须遍历整个链表才能找到倒数第二个节点(例如:temp = head; while(temp->Next->Next != NULL) { temp = temp->Next; } temp->next = NULL; Tail = temp;
每次出队都需要遍历链表,因此时间复杂度为 Θ(n)。
选项 (B) 正确。
15
以下哪一项是队列数据结构的应用?( )
(A) 当资源在多个消费者之间共享时:
在需要将资源(如打印机、CPU 时间或数据库连接)共享给多个消费者或进程的场景中,可以使用队列数据结构。每个消费者可将自己的请求入队,资源按请求顺序通过出队分配给他们。这确保了对共享资源的公平访问,并防止冲突或资源竞争。(B) 当两个进程之间异步传输数据时:
当两个进程或系统之间异步传输数据时,队列可用作缓冲区或中间存储。一个进程将待发送的数据入队,另一个进程则出队并处理接收到的数据。队列允许解耦数据生产与消费的速率,确保进程间通信的流畅与高效。(C) 负载均衡:
负载均衡是将工作负载分布到多个资源以优化性能和利用率的实践。队列数据结构可用于负载均衡算法中管理传入请求或任务。请求被入队到队列中,负载均衡器可根据不同标准(如轮询、最少连接数)出队并分配给可用资源。这有助于在资源间均匀分配工作负载,避免过载并最大化吞吐量。结论:由于队列在资源共享、异步通信和负载均衡中均能有效应用,因此选项 (D) 是正确的。
16
考虑以下陈述:
i. 先进先出(FIFO)类型的计算通过 栈(STACKS)高效支持。
ii. 使用链表实现 列表(LISTS)比使用数组实现列表在几乎所有基本操作中更高效。
iii. 使用 环形数组 实现队列(QUEUES)比使用带有两个索引的线性数组实现队列更高效。
iv. 后进先出(LIFO)类型的计算通过 队列(QUEUES)高效支持。
以下哪项是正确的?( )
正确陈述是:iii. 使用环形数组实现队列比使用线性数组和两个索引更高效。
解释:
- i. 栈用于实现后进先出(LIFO)操作,而非先进先出(FIFO)操作。因此,该陈述错误。
- ii. 链表在中间插入/删除操作上效率更高,但随机访问效率低于数组。因此,该陈述错误。
- iii. 环形数组通过循环利用空间避免了频繁的数组扩容和数据迁移,使入队/出队操作时间复杂度稳定为 O(1)。因此,该陈述正确。
- iv. 队列用于实现先进先出(FIFO)操作,而非后进先出(LIFO)操作。因此,该陈述错误。
综上,选项 (C) 是正确答案。
17
以下哪个选项不正确?( )
C
解析:
- 选项 A 正确:链表实现的队列中,插入元素时仅需更新尾指针
rear
,头指针front
不变。 - 选项 B 正确:队列可支持 LRU 页面置换(维护访问顺序)和快速排序(划分分区时的辅助结构)。
- 选项 C 错误:队列既能用于快速排序,也能用于 LRU 算法,其断言“不能用于 LRU”与事实矛盾。
- 选项 D 错误:A 正确而 C 错误,因此 D 的“所有选项均不正确”不成立。
综上,选项 C 是唯一错误的陈述。
18
假设一个容量为 (n-1)
的循环队列使用大小为 n
的数组实现。插入和删除操作分别通过 REAR
和 FRONT
作为数组索引变量完成。初始时,REAR = FRONT = 0
。检测队列满和队列空的条件是( ):
解析:
初始化状态
- 数组大小 $ n = 5 $
- 初始时
FRONT = REAR = 0
入队操作示例
enqueue("a"); REAR = (REAR+1)%5; (FRONT = 0, REAR = 1) enqueue("b"); REAR = (REAR+1)%5; (FRONT = 0, REAR = 2) enqueue("c"); REAR = (REAR+1)%5; (FRONT = 0, REAR = 3) enqueue("d"); REAR = (REAR+1)%5; (FRONT = 0, REAR = 4)
- 此时队列已满(容量 4),触发上溢条件
条件验证
队列满条件
$(REAR + 1) \mod n = (4 + 1) \mod 5 = 0$FRONT
也为 0 → 满足(REAR+1) mod n == FRONT
队列空条件
空时REAR == FRONT
(如初始状态)
结论
选项 A 的两个条件均成立,符合循环队列设计规范。
19
在实现复杂系统的事件驱动模拟(如计算机网络模拟或交通模拟)时,通常使用哪种数据结构?( )
在实现复杂系统的事件驱动模拟(如计算机网络模拟或交通模拟)时,常用的数据结构是队列。在事件驱动模拟中,事件会在特定时间发生,这些事件需要按照其发生的顺序进行处理。队列遵循先进先出(FIFO)原则,这使其非常适合维护事件的顺序。因此选项 (D) 是正确答案。
20
存储元素严格递增或严格递减的双端队列称为( )。
定义
单调双端队列是一种存储元素严格递增或严格递减的双端队列。为保持单调性,需要删除元素。
操作示例
- 假设存在单调(递减)双端队列
dq = {5, 4, 2, 1}
- 当插入元素
3
时,需从尾部删除元素直至满足dq.back() < 3
- 删除过程:移除
2
和1
- 最终结果:
dq = {5, 4, 3}
结论
通过上述特性可知,选项 (C) 是正确答案。
21
考虑以下程序,确定该函数的作用。
( )
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
struct Node* left;
struct Node* right;
};
struct Node* newNode(int item) {
struct Node* node = (struct Node*)malloc(sizeof(struct Node));
node->data = item;
node->left = node->right = NULL;
return node;
}
void function(struct Node* root) {
if (root == NULL)
return;
struct Node** q = (struct Node**)malloc(100 * sizeof(struct Node*));
int front = 0, rear = 0;
q[rear++] = root;
while (front < rear) {
struct Node* node = q[front++];
printf("%d ", node->data);
if (node->left != NULL)
q[rear++] = node->left;
if (node->right != NULL)
q[rear++] = node->right;
}
free(q);
}
上述代码使用队列实现了树的层序遍历。其核心逻辑如下:
- 队列机制:通过先进先出(FIFO)的队列特性,确保节点按层级顺序访问。
- 处理流程:
- 将根节点入队
- 循环处理队列中的节点
- 每次取出队首节点并访问
- 将该节点的子节点依次入队
- 效果验证:这种处理方式保证了同一层级的节点被连续访问,下一层级的节点在当前层级处理完成后才开始访问,符合层序遍历的定义。
因此选项 (C) 是正确答案。
22
以下哪项是循环队列的优势( )?
循环队列的应用场景:
- 内存管理:普通队列中未使用的内存位置可以通过循环队列实现复用。
- 交通系统:在计算机控制的交通系统中,循环队列用于按照设定时间周期性地切换交通信号灯。
- CPU 调度:操作系统通常维护一个就绪执行或等待特定事件发生的进程队列。因此选项 (D) 为正确答案。
23
实现双端队列(deque)通常使用哪种数据结构?( )
- 解析:
- 双端队列可以通过双向链表或循环数组两种方式实现
- 在这两种实现方案中,所有操作的时间复杂度都可以达到 O(1)
- 因此选项 (D) 是正确答案
24
以下哪项是优先队列的类型?( )
优先队列的类型包括:
升序优先队列
在升序优先队列中,优先级值较低的元素在优先级列表中具有更高的优先级。降序优先队列
最大堆中的根节点是最大元素。它会首先移除优先级最高的元素。
因此选项 (D) 是正确答案。
25
给定一个使用单向链表实现的队列,Rear 指针指向队列的尾节点,front 指针指向队列的头节点。以下哪项操作无法在 O(1) 时间内完成?( )
解释:
- 队列是一种线性数据结构,遵循先进先出(FIFO)原则。
- 在链表实现的队列中:
- 删除前端元素(头节点):通过
front
指针直接操作,时间复杂度为 O(1)。 - 删除尾部元素(尾节点):需从头节点遍历至倒数第二个节点,修改其指针为
NULL
,时间复杂度为 O(N)。 - 插入前端元素:违反队列规则,通常不执行此操作。
- 删除前端元素(头节点):通过
- 因此,删除尾部元素 是唯一无法在常数时间内完成的操作。
6 - 栈
1
以下是一个错误的伪代码,该算法本应判断括号序列是否平衡:
创建一个字符栈
while(还有输入可用) {
读取一个字符
if(该字符是 '(')
将其压入栈中
else if(该字符是 ')' 且栈不为空)
弹出栈顶字符
else
打印 "unbalanced" 并退出
}
打印 "balanced"
上述代码会将下列哪个不平衡序列误判为平衡?( )
- 关键问题:在
while
循环结束后,必须检查栈是否为空。 - 示例分析:对于输入
((())
,循环结束后栈不为空(剩余一个未匹配的'('
),但代码未执行此检查,导致误判为平衡。
2
使用一个单数组 A[1..MAXSIZE]
实现两个栈。这两个栈从数组的两端向中间增长。变量 top1
和 top2
(top1 < top2
)分别指向每个栈的栈顶元素位置。若要高效利用空间,则“栈满”的条件是( ):
解释:
- 该设计允许两个栈动态共享数组空间
- 当
top1 = top2 - 1
时,表示两个栈顶指针相邻 - 此时数组中已无可用空间(因
top1 < top2
的约束) - 其他条件均未体现双栈协同增长的特性
- 该条件能最大化利用数组存储空间
3
评估一个没有嵌套函数调用的表达式时( ):
任何表达式都可以转换为后缀或前缀形式。前缀和后缀表达式的计算可以通过单个栈完成。例如:给定表达式 ‘10 2 8 * + 3 -’,操作步骤如下:
- 将 10 压入栈
- 将 2 压入栈
- 将 8 压入栈
- 遇到运算符 ‘*’ 时,弹出 2 和 8,计算 2*8=16 后压入栈
- 遇到运算符 ‘+’ 时,弹出 16 和 10,计算 10+16=26 后压入栈
- 将 3 压入栈
- 遇到运算符 ‘-’ 时,弹出 26 和 3,计算 26-3=23 后压入栈
最终结果 23 是通过单个栈得到的。因此选项 (B) 正确。
4
计算后缀表达式 10 5 + 60 6 / * 8 -
的结果是( ):
步骤 1:后缀表达式求值规则
操作数(数字)被压入栈中。遇到运算符时,从栈中弹出所需数量的操作数,执行运算,并将结果重新压入栈中。
步骤 2:逐步求值
表达式:10 5 + 60 6 / ∗ 8 −
- 压入 10:栈 = [10]
- 压入 5:栈 = [10, 5]
- 遇到
+
:弹出 10 和 5,相加:10+5=15。将结果压入栈:栈=[15] - 压入 60:栈 = [15, 60]
- 压入 6:栈 = [15, 60, 6]
- 遇到
/
:弹出 60 和 6,相除:60/6=10。将结果压入栈:栈=[15,10] - 遇到
*
:弹出 15 和 10,相乘:15×10=150。将结果压入栈:栈=[150] - 压入 8:栈 = [150, 8]
- 遇到
-
:弹出 150 和 8,相减:150−8=142。将结果压入栈:栈=[142]
步骤 3:栈中的最终结果
最终结果为 142
5
假设使用链表而非数组实现一个栈。对于使用链表实现的栈(假设实现方式高效),其入栈和出栈操作的时间复杂度会受到什么影响?
- 插入操作(入栈):在链表头部插入新节点时,仅需修改头指针和新节点的引用关系,无需遍历链表,因此时间复杂度为 O(1)。
- 删除操作(出栈):移除链表头部节点时,同样只需调整头指针指向下一个节点,操作时间为 O(1)。
- 链表实现栈的核心优势在于始终在头部进行操作,避免了尾部操作所需的遍历开销。
6
考虑有 n 个元素均匀分布在 k 个栈中。每个栈中的元素按升序排列(每个栈的顶部是最小值,向下依次递增)。
现有一个容量为 n 的队列,需要将所有 n 个元素按升序放入其中。已知最优算法的时间复杂度是多少?( )
核心思路:构建大小为 k 的最小堆
- 初始阶段将所有栈顶元素加入堆(共 k 个元素)
- 每次从堆中提取最小值后,从对应栈取出下一个元素并重新插入堆
- 重复此过程直至所有元素入队
时间复杂度分析:
- 堆初始化:$O(k)$
- 后续操作:每轮堆操作耗时 $O(\log k)$,共需执行 $n - k$ 次
- 总时间复杂度:$O(k) + (n - k)\log k = O(n \log k)$
7
假设输入序列是 5, 6, 7, 8, 9(按此顺序),以下哪种排列可以通过栈操作得到相同的顺序?( )
选项 (C) 的解析说明
- 该序列可通过以下栈操作实现:
- Push 5
- Push 6
- Push 7
- Pop 7
- Push 8
- Pop 8
- Push 9
- Pop 9
- Pop 6
- Pop 5
- 栈遵循先进后出原则,上述操作严格满足输入顺序与输出序列的匹配关系
- 最终输出结果为 7, 8, 9, 6, 5
8
检查算术表达式中括号是否匹配的最佳数据结构是( )
- 原因:栈的后进先出(LIFO)特性与括号匹配需求高度契合
- 操作流程:
- 遇到左括号
(
时,将其压入栈 - 遇到右括号
)
时,检查栈顶元素是否为对应左括号 - 若栈为空或类型不匹配,则括号不匹配
- 表达式处理完成后,若栈为空则表示完全匹配
- 遇到左括号
- 优势对比:
- 队列(FIFO)无法保证最近左括号优先匹配
- 树/列表缺乏顺序性约束,需额外维护索引
9
如果在栈上执行以下操作序列:push(1)、push(2)、pop、push(1)、push(2)、pop、pop、pop、push(2)、pop,那么弹出值的顺序是( ):
解析过程:
Push(1)
→ 栈 = [1]Push(2)
→ 栈 = [1, 2]Pop
→ 弹出 2,栈 = [1]Push(1)
→ 栈 = [1, 1]Push(2)
→ 栈 = [1, 1, 2]Pop
→ 弹出 2,栈 = [1, 1]Pop
→ 弹出 1,栈 = [1]Pop
→ 弹出 1,栈 = []Push(2)
→ 栈 = [2]Pop
→ 弹出 2,栈 = []
最终弹出顺序: 2, 2, 1, 1, 2
10
考虑以下对一个大小为 5 的栈执行的操作:Push(a)
;Pop()
;Push(b)
;Push(c)
;Pop()
;Push(d)
;Pop()
;Pop()
;Push(e)
。以下哪项陈述是正确的?( )
解析
- 初始空栈容量为 5
- 操作序列分析:
- Push(a) → [a]
- Pop() → []
- Push(b) → [b]
- Push(c) → [b, c]
- Pop() → [b]
- Push(d) → [b, d]
- Pop() → [b]
- Pop() → []
- Push(e) → [e]
- 所有操作均未超出栈容量限制
- 最终栈状态为 [e]
- 因此选项 (B) 正确,栈操作顺利执行
11
以下哪项不是栈的固有应用?( )
- 栈的典型应用场景包括:
- 字符串反转(D)
- 后缀表达式求值(B)
- 递归函数调用(A)
- 作业调度(C)通常由操作系统中的队列管理,而非栈结构
- 因此选项 C 不属于栈的固有应用
12
考虑以下在空栈上的操作序列:
Push(54);
Push(52);
Pop();
Push(55);
Push(62);
s = Pop();
考虑以下在空队列上的操作序列:
Enqueue(21);
Enqueue(24);
Dequeue();
Enqueue(28);
Enqueue(32);
q = Dequeue();
s + q 的值是( )。
我们构建一个空栈并执行操作。栈遵循后进先出(LIFO)顺序:
- Push(54) // (54)
- Push(52) // (54,52)
- Pop() // (54)
- Push(55) // (54,55)
- Push(62) // (54,55,62)
- s = Pop() // (54,55) s=62
我们构建一个空队列并执行操作。队列遵循先进先出(FIFO)顺序:
- Enqueue(21) // [21]
- Enqueue(24) // [21,24]
- Dequeue() // [24]
- Enqueue(28) // [24,28]
- Enqueue(32) // [24,28,32]
- q = Dequeue() // [28,32] q=24
因此,s + q = 62 + 24 = 86。
另一种方式:
- 栈是后进先出的数据结构,因此 s = Pop() = 62
- 队列是先进先出的数据结构,因此 q = Dequeue() = 24
- 所以 s + q = 62 + 24 = 86。
13
以下关于使用链表实现栈的说法中,哪一项是正确的?( )
为了保持后进先出(LIFO)的顺序,可以使用链表以两种方式实现栈:
- a) 在
push
操作中,如果新节点插入到链表的开头,则在pop
操作中必须从开头删除节点。 - b) 在
push
操作中,如果新节点插入到链表的末尾,则在pop
操作中必须从末尾删除节点。
14
堆栈 A 中包含元素 a、b、c(a 在顶部),堆栈 B 为空。从堆栈 A 弹出的元素可以立即打印或压入堆栈 B,而从堆栈 B 弹出的元素只能被打印。在这种安排下,以下哪个排列顺序不可能实现?( )
选项 (A):
- 从堆栈 A 弹出 a
- 将 a 压入堆栈 B
- 打印 b
- 从堆栈 B 弹出 a 并打印
- 从堆栈 A 弹出 c 并打印
输出顺序:b a c
选项 (B):
- 从堆栈 A 弹出 a
- 将 a 压入堆栈 B
- 从堆栈 A 弹出 b 并打印
- 从堆栈 A 弹出 c 并打印
- 从堆栈 B 弹出 a 并打印
输出顺序:b c a
选项 (C):
- 从堆栈 A 弹出 a → 压入堆栈 B
- 从堆栈 A 弹出 b → 压入堆栈 B
- 从堆栈 A 弹出 c → 直接打印
此时堆栈 B 内容为 [a, b](a 在顶部)
后续操作限制:
- 必须先弹出 b 才能弹出 a
- 导致输出顺序为
c b a
或c a b
(需先弹出 b)
因此c a b
无法实现
选项 (D):
- 从堆栈 A 依次弹出 a → 打印
- 弹出 b → 打印
- 弹出 c → 打印
输出顺序:a b c
15
将五个元素 A、B、C、D 和 E 依次入栈(从 A 开始)。随后将栈中弹出四个元素并依次插入队列。接着从队列中删除两个元素并重新压入栈。此时从栈中弹出一个元素,该元素是( ):
当五个元素 A、B、C、D 和 E 依次入栈时:
栈的顺序为:A、B、C、D、E(A 在底部,E 在顶部)弹出四个元素并插入队列后:
队列的顺序为:B、C、D、E(B 在队尾,E 在队首)
弹出操作后的栈仅剩:A从队列中删除两个元素并重新压入栈后:
新栈的顺序为:A、E、D(A 在底部,D 在顶部)最终操作结果:
由于 D 位于栈顶,因此弹出操作会取出 D
所以,正确选项是 (D)。
16
考虑以下陈述:
i. 先进先出(FIFO)类型的计算通过栈(STACKS)可以高效支持。
ii. 在链表上实现列表(LISTS)比在数组上实现列表对于几乎所有基本列表操作都更高效。
iii. 在循环数组上实现队列(QUEUES)比在使用两个索引的线性数组上实现队列更高效。
iv. 后进先出(LIFO)类型的计算通过队列(QUEUES)可以高效支持。
以下哪项是正确的?( )
正确陈述是:iii. 在循环数组上实现队列比在使用两个索引的线性数组上实现队列更高效。
解释:
- i. 错误:栈(STACKS)支持 LIFO 操作,而非 FIFO。FIFO 由队列(QUEUES)实现。
- ii. 错误:链表在插入/删除操作上更高效,但随机访问不如数组高效。
- iii. 正确:循环数组通过索引环绕机制提升队列操作效率。
- iv. 错误:队列(QUEUES)实现 FIFO,LIFO 由栈(STACKS)实现。
综上,选项 (C) 是正确答案。
17
实现一个队列所需的最小栈数量是( )
实现原理:
- 使用两个栈 S₁ 和 S₂
- 新元素入队时,压入 S₁ 栈顶
- 出队操作时:
- 若 S₂ 为空,则将 S₁ 所有元素依次弹出并压入 S₂
- 直接弹出 S₂ 栈顶元素(即队首)
时间复杂度分析:
- 入队操作:O(1)
- 出队操作:均摊 O(1)(每个元素最多被压入/弹出两次)
结论:
通过这种双栈结构,既能保证先进先出的队列特性,又能达到最优的时间效率。因此最少需要 2 个栈 实现队列,选项 (C) 正确。
18
使用逆波兰表示法在栈机器上计算表达式 (A * B) + (C * D / E)
需要多少次 PUSH 和 POP 操作?( )
逆波兰表示法是一种无需括号或特殊标点的公式表示方法。
计算 (A ∗ B) + (C ∗ D / E) 的过程如下:
- 去除括号和标点,将其转换为后缀形式 AB+CDE/*+
- 将 AB 压入栈 → 执行 * 操作时弹出 AB 并计算 A*B,将结果(设为 X)重新压入栈
- 将 CDE 压入栈 → 执行 / 操作时弹出 DE 并计算 C*D/E,将结果(设为 Y)重新压入栈
- 执行 * 操作时弹出 CY 并计算 Y*C,将结果(设为 Z)重新压入栈
- 最后执行 + 操作时弹出 XZ 并计算 X+Z,将最终结果压入栈
上述计算过程共包含 5 次 PUSH 和 4 次 POP 操作。因此选项 (B) 正确。
7 - 二叉树
1
以下关于二叉树的说法中,哪一项是正确的( )?
E
概念定义
- 满二叉树:除叶子节点外的所有节点都有两个子节点的树(严格二叉树/2-树)。
- 完全二叉树:除最后一层外,每一层完全填满且节点尽可能靠左排列的二叉树。
选项分析
A) 错误。示例:12 / 20 / 30
此树既不是完全二叉树(最后一层未填满且未靠左),也不是满二叉树(存在单子节点)。
B) 错误。示例:
12 / \ 20 30 / 30
此树是完全二叉树(满足完全性),但非满二叉树(根节点右子树缺失)。
C) 错误。示例:
12 / \ 20 30 / \ 20 40
此树是满二叉树(所有非叶节点有两个子节点),但非完全二叉树(最后一层未靠左填满)。
D) 错误。示例:
12 / \ 20 30 / \ 10 40
此树同时满足完全二叉树(结构完整且靠左)和满二叉树(所有非叶节点有两个子节点)。
结论
所有选项均存在反例,因此正确答案为 E。
2
如果运算符的元数是固定的,那么以下哪种表示法可以用于无需括号解析表达式?( )
a) 中缀表示法(表达式树的中序遍历)
b) 后缀表示法(表达式树的后序遍历)
c) 前缀表示法(表达式树的先序遍历)
解释:
- 先序(前缀)表示法和后序(后缀)表示法均能通过操作符/操作数的排列顺序隐含运算优先级,无需依赖括号。
- 中缀表示法因需区分运算符优先级和结合性,通常需要括号辅助解析。
- 题干中标准答案标注为 A(a、b 和 c),但严格来说中缀表示法不符合“无需括号”的条件。此答案可能存在矛盾,建议结合具体上下文判断。
3
节点的层级是从根节点到该节点的距离。例如,根节点的层级为 1,根节点的左右子节点层级为 2。二叉树第 i 层上最多可能包含的节点数是( )。
解析:
当二叉树为完全满二叉树时节点数最大,因此答案应为 $2^{(i-1)}$
所以选项 (A) 正确。
4
在完全 $k$ 叉树中,每个内部节点恰好有 $k$ 个子节点或没有子节点。当该树包含 $n$ 个内部节点时,其叶子节点的数量为( ):
对于每个节点恰好有 k 个子节点或没有子节点的 k 叉树,以下关系成立:
叶子节点数量公式
$$ L = (k-1) \times n + 1 $$
其中 $ L $ 表示叶子节点数量,$ n $ 表示内部节点数量。
示例说明
观察下图结构:
o
/ | \
o o o
/ | \ / | \
o o o o o o
/ | \
o o o
- 参数取值:$ k = 3 $, 内部节点数 $ n = 4 $
- 计算过程: $$ L = (3-1) \times 4 + 1 = 8 + 1 = 9 $$
- 验证结果:图中共有 9 个叶子节点(最底层的 6 个 + 中间层的 3 个)
5
使用三个无标签的节点最多可以形成多少棵二叉树( )?
解析:
- 当节点无标签时,二叉树的形态仅由结构决定
- 可通过卡特兰数(Catalan numbers)计算二叉树数量
- 计算公式如下:
$$ \frac{\binom{2n}{n}}{n+1} = \frac{\binom{6}{3}}{4} = 5 $$
- 因此当 $ n = 3 $ 时,共有 5 种不同形态 的二叉树
- 若节点有标签,则每种结构对应 $ 3! = 6 $ 种排列,总数为 $ 5 \times 6 = 30 $ 棵
- 所以选项 (B) 正确
6
一棵有 $n$ 个节点的根树中,每个节点有 $0$ 或 $3$ 个子节点。该树的叶节点数量为( ):
在节点数为 n 的树中,部分节点有 0 或 3 个子节点:定义如下变量:
n
:总节点数L
:叶节点数I
:内部节点数
关键关系是:每个具有 3 个子节点的内部节点会生成 2 个内部节点和 1 个叶节点。根节点不参与此计算,因此可得公式 $ L = 2I + 1 $。同时,总节点数满足 $ n = L + I $。
将第一个公式代入第二个公式:
$$ n = (2I + 1) + I \quad \Rightarrow \quad n = 3I + 1 $$
解得 $ I = \frac{n-1}{3} $。
将其代回 $ L = 2I + 1 $:
$$ L = 2\left(\frac{n-1}{3}\right) + 1 \quad \Rightarrow \quad L = \frac{2n+1}{3} $$
因此,此类树的叶节点数为 $ \frac{2n+1}{3} $。
7
一个权重平衡树是一棵二叉树,其中每个节点的左子树中的节点数至少是右子树的一半,并且最多是右子树的两倍。对于包含 $ n $ 个节点的此类树,其最大可能的高度(从根到最远叶子路径上的节点数)最好用以下哪项描述?
权重平衡树是一种二叉树,其中每个节点的左子树中的节点数至少是右子树的一半,并且最多是右子树的两倍。为了确定这种树在 $ n $ 个节点下的最大可能高度,我们分析选项:
- a) 表示完全平衡二叉树的高度。在权重平衡树中,左子树最多可以是右子树的两倍,因此它不一定是完全平衡的。此选项未考虑约束条件。
- b) 对数底数 $\log_{4/3} n$ 是非标准选择。该值不符合二叉树高度分析的典型模式。
- c) 表示完全平衡三叉树的高度。与二叉树的权重平衡约束无关,无法反映实际结构特性。
- d) 底数 $\log_{3/2} n$ 反映了左子树与右子树的节点数比例关系。由于左子树规模在右子树的 $[1/2, 2]$ 倍范围内,该对数形式能准确描述树的最大高度增长趋势。
8
在完全 $n$ 叉树中,每个节点要么有 $n$ 个子节点,要么没有子节点。设 $I$ 为内部节点数,$L$ 为叶子节点数。若 $L = 41$ 且 $I = 10$,求 $n$ 的值( )。
对于每个节点有 n 个子节点或无子节点的 n 叉树,满足以下关系:
$$ L = (n-1) \times I + 1 $$
(其中 $ L $ 为叶子节点数,$ I $ 为内部节点数)
计算过程
- 代入给定数据: $$ 41 = 10(n - 1) + 1 $$
- 移项并化简: $$ n - 1 = \frac{41 - 1}{10} = 4 $$
- 解得: $$ n = 5 $$
9
二叉树的高度是从根到任意叶子路径中的最大边数。高度为 h 的二叉树中节点的最大数量是( ):
节点数最多的将是完全二叉树。
高度为 $ h $ 的完全二叉树的节点数计算如下:
$$ 1 + 2 + 2^2 + 2^3 + \cdots + 2^h = 2^{h+1} - 1 $$
该公式来源于等比数列求和,其中每一层的节点数是前一层的两倍,最终总和为 $ 2^{h+1} - 1 $。
10
将二叉树存储到数组 X
中的方案如下:X
的索引从 1
开始而非 0
。根节点存储在 X[1]
中。对于存储在 X[i]
处的节点,其左子节点(如果存在)存储在 X[2i]
中,右子节点(如果存在)存储在 X[2i+1]
中。为了能够存储任意 n
个顶点的二叉树,X
的最小大小应为多少?( )
对于右偏二叉树,节点数将是 $2^n - 1$。例如,在下述二叉树中,节点 ‘A’ 将存储在索引 1 处,‘B’ 存储在索引 3 处,‘C’ 存储在索引 7 处,‘D’ 存储在索引 15 处:
A
\
\
B
\
\
C
\
\
D
该存储方式要求数组 X 的大小必须能够容纳最坏情况下的索引位置。当二叉树退化为单链结构(完全右偏)时,第 k 层节点的索引为 $2^k - 1$。若总共有 n 个节点,则最大索引值为 $2^n - 1$,因此数组最小大小需为 $2^n - 1$。其他选项均无法覆盖此类极端情况。
11
对给定的二叉搜索树 T 进行后序遍历,得到以下键值序列:
10, 9, 23, 22, 27, 25, 15, 50, 95, 60, 40, 29
以下哪一组键值序列可能是该树 T 的中序遍历结果?( )
解析
- 二叉搜索树的中序遍历具有严格性质:输出结果必为升序排列
- 对比各选项:
- 选项 A:
9, 10, 15, 22, 23, 25, 27, 29, 40, 50, 60, 95
→ 完全升序 ✅ - 其他选项均存在逆序元素 ❌
- 选项 A:
- 因此唯一符合条件的选项是 A
12
考虑以下二叉树的嵌套表示法:(X Y Z)
表示节点 X
的左右子树分别为 Y
和 Z
。注意 Y
和 Z
可能为 NULL
,或进一步嵌套。以下哪项表示一个有效的二叉树?
解析
C 是正确的
(1 (2 3 4)(5 6 7))
表示如下二叉树:1 / \ 2 5 / \ / \ 3 4 6 7
A) (1 2 (4 5 6 7)) 不合法
括号内包含 4 个元素(4 5 6 7)
,违反了二叉树每个节点最多两个子节点的规则。B) (1 (2 3 4) 5 6) 7) 不合法
存在括号不匹配问题(2 个左括号 vs. 3 个右括号),且根节点有 4 个直接子元素。D) (1 (2 3 NULL) (4 5)) 不合法
右子树(4 5)
仅有 2 个元素,而根据表示法(X Y Z)
需要 3 个元素(父节点 + 左子树 + 右子树)。
13
考虑一棵二叉树中的一个节点 X。已知 X 有两个子节点,设 Y 为 X 的中序后继。以下关于 Y 的说法正确的是( )?
解析:
在二叉树中,若节点 X 有两个子节点,则其 中序后继 Y 是 X 的右子树中最左侧的节点。
- 根据中序遍历(左 - 根 - 右)的特性,X 的中序后继必然位于其右子树中。
- 最左侧的节点意味着该节点 没有左子节点(否则会继续向左搜索)。
因此,Y 的左子节点必为空,选项 B 正确。
14
树的高度是指从根节点到叶节点的最长路径上的边数。高度为 5 的二叉树中,节点的最大数和最小数分别是( )
最大节点数:当二叉树是完美二叉树时,节点数最大。
高度为 $ h $ 的完美二叉树的节点数为 $ 2^{h+1} - 1 $。最小节点数:当二叉树是倾斜二叉树时,节点数最小。
此时节点数为 $ h + 1 $。
根据题意并分析选项可知,选项 (A) 正确。
15
一棵二叉树 T 有 20 个叶子节点。该树中具有两个子节点的节点数量是( )。
所有度数之和 = 2 × |边数|。将树视为 k 叉树时:叶子节点度数之和 + 非根内部节点度数之和 + 根节点度数 = 2 × (节点总数 - 1)
推导过程:
- 代入公式: $$ L + (I-1)(k+1) + k = 2(L + I - 1) $$
- 展开并化简: $$ \begin{align*} L + kI - k + I - 1 + k &= 2L + 2I - 2 \\ L + kI + I - 1 &= 2L + 2I - 2 \\ (k-1)I + 1 &= L \end{align*} $$
- 代入已知条件 $k=2$,$L=20$: $$ (2-1)I + 1 = 20 \Rightarrow I = 19 $$
结论:
树中有 19 个具有两个子节点的内部节点。
16
大小为 n 的整数数组可以通过从完全二叉树中索引为 ⌊(n - 1)/2⌋ 的节点开始,依次向上调整每个内部节点的堆结构(直到根节点 0)来转换为堆。这种构造方式所需的时间复杂度是:( )
上述描述实际上是输入数组 A 的堆构建算法。
BUILD-HEAP(A)
heapsize := size(A);
for i := floor(heapsize/2) downto 1
do HEAPIFY(A, i);
end for
END
该算法的时间复杂度上界为 $O(n)$。
17
在一棵二叉树中,每个节点的左子树和右子树中的节点数之差最多为 2。如果树的高度为 h > 0,则该树的最小节点数是( )。
设高度为 h 时的节点数为 $n(h)$。
在完美二叉树(除叶子节点外每个节点都有两个子节点)中,以下递推关系成立:
$$n(h) = 2 \times n(h-1) + 1$$
在本题情况下,节点数少 2 个,因此
$$n(h) = 2 \times n(h-1) + 1 - 2$$
$$= 2 \times n(h-1) - 1$$
将所有选项代入验证,只有选项 (B) 满足上述递推关系。
我们来看选项 (B):
$$n(h) = 2^{h-1} + 1$$
若代入 $n(h-1) = 2^{h-2} + 1$,应得到 $n(h) = 2^{h-1} + 1$:
$$ \begin{align*} n(h) &= 2 \times n(h-1) - 1 \\ &= 2 \times (2^{h-2} + 1) -1 \\ &= 2^{h-1} + 1 \end{align*} $$
18
从根节点开始对二叉树执行广度优先搜索(BFS)。存在一个顶点 t,其距离根节点为 4。如果 t 是该 BFS 遍历中的第 n 个顶点,则 n 的最大可能值是多少?( )
当给定距离为 4 时,节点编号为 31。
分析过程:
- 完全二叉树特性:在 BFS 中,若某层的所有节点均被访问,则下一层的起始位置为当前层末尾 + 1。
- 层级关系:距离根节点为 4 的节点位于第 5 层(根节点为第 0 层)。
- 最大节点数计算:
完全二叉树的总节点数公式为 $2^{h+1} - 1$,其中 h 为高度。
当高度为 4 时,总节点数为 $2^{5} - 1 = 31$,即第 5 层最后一个节点为第 31 个节点。
示例说明:
以距离为 2 的情况为例,第 3 层最远节点 G 在 BFS 中的序号为 7:
A
/ \
B C
/ \ / \
D E F G
结论:
在距离 4 处,最后一个节点的序号为 31。选项 (C)。
19
在一棵二叉树中,度为 1 的内部节点数为 5,度为 2 的内部节点数为 10。该二叉树中的叶子节点数是( ):
解释:
在二叉树中,叶子节点的数量与度为 2 的内部节点之间存在以下关系:
叶子节点数 = 度为 2 的内部节点数 + 1
根据题目条件:
- 度为 2 的内部节点数 = 10
- 度为 1 的内部节点数不影响叶子节点数量
代入公式:
叶子节点数 = 10 + 1 = 11
20
已知以下三个序列分别是某二叉树的先序、中序和后序遍历结果,但顺序未知。请从下列选项中选出正确的陈述( )。
I. MBCAFHPYK
II. KAMCBYPFH
III. MABCKYFPH
解题思路
- 首先找出首尾元素相同的序列:
- 先序遍历的第一个元素是根节点
- 后序遍历的最后一个元素是根节点
- 观察给出的序列:
- 先序 = KAMCBYPFH
- 后序 = MBCAFHPYK
- 剩余序列 MABCKYFPH 将是中序遍历
- 确认三组序列对应关系:
- I. 后序
- II. 先序
- III. 中序
- 首先找出首尾元素相同的序列:
结论
根据上述分析,选项 D 正确描述了序列与遍历方式的对应关系。
21
一棵包含 $ n > 1 $ 个节点的二叉树中,分别有 $ n_1 $、$ n_2 $ 和 $ n_3 $ 个度数为一、二和三的节点。节点的度数定义为其相邻节点的数量。$ n_3 $ 可以表示为( )。
解释
对于任意树结构,所有节点的度数之和等于边数的两倍(即 $ \sum \text{度数} = 2(n-1) $)。设总节点数为 $ n = n_1 + n_2 + n_3 $,则:
$$ 1 \cdot n_1 + 2 \cdot n_2 + 3 \cdot n_3 = 2(n - 1) $$
将 $ n = n_1 + n_2 + n_3 $ 代入并化简,可得:
$$ n_3 = n_1 - 2 $$
推导过程:
树的性质:
任意树的边数为 $ n - 1 $,根据握手定理,所有节点的度数之和等于边数的两倍:$$ \sum_{v \in V} \deg(v) = 2(n - 1) $$
度数分类求和:
设 $ n_1, n_2, n_3 $ 分别表示度数为 1、2、3 的节点数量,则:$$ 1 \cdot n_1 + 2 \cdot n_2 + 3 \cdot n_3 = 2(n - 1) $$
总节点数关系:
$$ n = n_1 + n_2 + n_3 $$
联立方程消元:
将 $ n $ 代入度数方程,整理后得到: $$ n_3 = n_1 - 2 $$
该结论表明,在仅包含度数为 1、2、3 的树中,度数为 3 的节点数量比度数为 1 的节点少 2。
22
一棵具有 $n > 1$ 个节点的二叉树中,分别有 $n_1$、$n_2$ 和 $n_3$ 个度为一、二和三的节点。节点的度定义为其相邻节点的数量。从上述树开始,在树中仍存在度为二的节点时,添加一条边连接该节点的两个相邻节点,然后移除该节点。最终将剩下多少条边?( )
解析
初始边数计算
树的边数恒为 $n - 1$,其中 $n = n_1 + n_2 + n_3$。操作影响分析
- 每次处理一个度为 2 的节点时:
- 添加一条边(连接其两个相邻节点),边数增加 1;
- 移除该节点及其两条原有边,边数减少 2;
- 净变化:边数减少 1。
- 每次处理一个度为 2 的节点时:
操作终止条件
当无度为 2 的节点时停止。设共执行 $k$ 次操作,则:- 最终边数 = 初始边数 $- k$;
- 初始边数 $= (n_1 + n_2 + n_3) - 1$;
- 最终边数 $= (n_1 + n_2 + n_3 - 1) - k$。
求解 $k$ 的值
- 每次操作消除一个度为 2 的节点,因此 $k = n_2$。
代入简化
- 最终边数 $= (n_1 + n_2 + n_3 - 1) - n_2 = n_1 + n_3 - 1$;
- 由于树的度数总和公式:$\sum (\text{度}) = 2(n - 1)$,可得 $n_1 + 2n_2 + 3n_3 = 2(n - 1)$;
- 化简后 $n_1 + n_3 = 2(n - 1) - n_2 - 2n_2 = 2n - 2 - 3n_2$;
- 代入最终边数表达式:$n_1 + n_3 - 1 = 2n - 3 - 3n_2$;
- 结合 $n = n_1 + n_2 + n_3$,进一步化简可得 $2n_1 - 3$。
综上,正确答案为 A。
23
一个二叉搜索树包含值 1、2、3、4、5、6、7、8。对该树进行前序遍历并输出值。以下哪项序列是有效的输出?( )
解析:
正确性分析
- 前序遍历特性:根节点 → 左子树 → 右子树
- 二叉搜索树规则:左子树所有节点 < 根节点 < 右子树所有节点
- 有效序列特征:任意子序列需满足 BST 的大小约束关系
选项 D 分析
序列:5 3 1 2 4 7 6 8
- 根节点 5 将序列划分为左子树 [1,2,3,4] 和右子树 [6,7,8]
- 左子树递归验证:
- 根节点 3 划分左 [1,2] 和右 [4]
- 节点 1 的右子树为 2(满足 1 < 2 < 3)
- 节点 4 无子节点
- 右子树递归验证:
- 根节点 7 划分左 [6] 和右 [8]
- 节点 6 无子节点
- 节点 8 无子节点
- 所有子树均满足 BST 性质
错误选项分析
- 选项 A (5 3 1 2 4 7 8 6)
- 在右子树 7 的子序列中,8 出现在 6 前面
- 8 属于 7 的右子树,6 应该是 7 的左子树
- 但 6 < 7 且 8 > 7,导致 8 和 6 同属 7 的不同子树却顺序颠倒
- 选项 B (5 3 1 2 6 4 8 7)
- 在左子树 3 的子序列中,6 出现在 4 前面
- 6 属于 3 的右子树,4 应该是 6 的左子树
- 但 4 < 6 且 4 > 3,导致 4 无法同时满足对父节点 3 和祖父节点 6 的约束
- 选项 C (5 3 2 4 1 6 7 8)
- 在左子树 3 的子序列中,2 的右子树出现 4(合法)
- 但 2 的左子树出现 1(合法),随后又出现 4(合法)
- 关键问题是 1 出现在 2 的右子树位置,违反 BST 规则(1 < 2 且 1 应该是 2 的左子树)
- 选项 A (5 3 1 2 4 7 8 6)
结论:只有选项 D 的序列能构建出符合 BST 性质的二叉搜索树
24
由 4 个节点组成的结构不同的二叉树可能有多少种?( )
解释
该问题可通过 卡特兰数(Catalan Number) 公式求解,用于计算具有 n
个不同节点的结构不同的二叉树总数。公式如下:
$$ C(n) = \frac{(2n)!}{n!(n+1)!} $$
当 n = 4
时:
- 分子:$ (2 \times 4)! = 8! = 40320 $
- 分母:$ 4! \times 5! = 24 \times 120 = 2880 $
- 结果:$ \frac{40320}{2880} = 14 $
因此,正确答案为 A. 14。
25
一个具有 10 个叶子节点的严格二叉树( )
一个具有 n 个叶节点的严格二叉树总是有 2n-1 个中间节点。当叶节点数为 10 时,严格二叉树将正好包含 19 个节点。因此,选项 (B) 正确。
26
具有 7 个节点的 AVL 树的最大高度是多少?(假设单个节点的树的高度为 0。)
参考:数据结构与算法 | 第 16 集
解析:
AVL 树的平衡性质要求任意节点的左右子树高度差不超过 1。要确定 7 个节点的 AVL 树的最大高度,需通过递推关系计算最小节点数:
$$ N(h) = N(h-1) + N(h-2) + 1 $$
其中 $N(h)$ 表示高度为 $h$ 的 AVL 树的最小节点数。初始条件为 $N(0) = 1$, $N(1) = 2$。
计算过程如下:
- $h=0$: $N(0) = 1$
- $h=1$: $N(1) = 2$
- $h=2$: $N(2) = N(1) + N(0) + 1 = 2 + 1 + 1 = 4$
- $h=3$: $N(3) = N(2) + N(1) + 1 = 4 + 2 + 1 = 7$
当 $h=3$ 时,最小节点数恰好为 7,因此 7 个节点的 AVL 树的最大高度为 3。
27
以下哪一项是红黑树的正确性质?( )
红黑树的性质(总结):
- 每个节点是红色或黑色
- 根节点是黑色
- 每个叶子节点(NIL,哨兵节点)是黑色
- 如果一个节点是红色的,则它的两个子节点都是黑色的(即不允许两个红色节点相邻)
- 从任一节点到其所有后代叶节点的每条路径都包含相同数目的黑色节点
选项分析:
- B 错误。红色节点不能有红色子节点。
- C 错误。红色节点的子节点必须是黑色。
- D 错误。红黑树的所有叶子节点(NIL节点)必须是黑色的。
所以正确答案为 A。
28
一个包含 $n$ 个非叶子节点的完全二叉树共有多少个节点( )?
在完全二叉树中,存在以下规律:
- 叶子节点数量 = 非叶子节点数量 + 1
- 当非叶子节点数为 $n$ 时,叶子节点数为 $n+1$
- 总节点数 = 非叶子节点数 + 叶子节点数 = $n + (n+1) = 2n+1$
29
假设你被给定一个包含 $n$ 个节点的二叉树,其中每个节点恰好有零个或两个子节点。该树的最大高度将是( )
解析
- 题目描述的是严格二叉树(每个非叶子节点必须有两个子节点)的最大高度问题
- 最大高度出现在最不平衡的二叉树结构中:
- 每层仅有一个非叶子节点
- 其余节点均为叶子节点
- 节点数量与高度的关系推导:
- 根节点为第 1 层
- 每增加 1 层需新增 2 个叶子节点
- 总节点数 $ n = 1 + 2(h-1) $
- 解得最大高度 $ h = \frac{n-1}{2} $
- 因此选项 C 是正确答案
30
包含 6 个节点的不同二叉树的数量是( )。
题目问的是“包含 6 个节点的不同二叉树的数量”,这实际上是一个 卡塔兰数(Catalan number) 的问题。
卡塔兰数定义:
卡塔兰数第 $n$ 项的公式为:
$$ C_n = \frac{1}{n+1} \binom{2n}{n} = \frac{(2n)!}{(n+1)! \cdot n!} $$
当 $n = 6$ 时:
$$ C_6 = \frac{1}{7} \binom{12}{6} = \frac{1}{7} \cdot 924 = 132 $$
所以选项 C 正确。
卡塔兰数常用于计算:
- 不同二叉树的数量
- 合法括号组合数
- 栈排序的排列数等结构问题
31
以下哪个节点数量可以构成一棵完全二叉树?( )
- 定义:完全二叉树是所有非叶子节点均有两个子节点的二叉树
- 性质:叶子节点数 $ L $ 与内部节点数 $ I $ 满足关系式
$$ L = I + 1 $$ - 验证过程:
- 总节点数 $ N = L + I = (I + 1) + I = 2I + 1 $
- 当 $ N = 15 $ 时,解得 $ I = 7, L = 8 $,满足完全二叉树条件
- 结论:选项 (B) 符合完全二叉树的构造规则
32
具有以下性质的完全二叉树:每个节点的值至少不小于其子节点的值,这种数据结构被称为( )
解析:
- 在最大堆(Max Binary Heap)中,每个节点的键值至少不小于其子节点的键值
- 在最小堆(Min Binary Heap)中,根节点的键必须是二叉堆中所有键中的最小值
- 题干描述的"每个节点的值至少不小于其子节点的值"符合最大堆的定义特征
- 因此正确答案为 (D) 堆
33
具有 $n$ 个叶子节点的满二叉树包含( )
- 定义:若每个节点有 0 或 2 个子节点,则称为满二叉树
- 性质:所有非叶子节点均有两个子节点
- 公式推导:
设叶子节点数为 $ n $,则总节点数为 $ 2n - 1 $ - 结论:选项 (C) 符合该数学关系
34
一个严格二叉树是指每个非叶子节点的左右子树都不为空的二叉搜索树。具有 19 个叶子节点的此类树( ):
对于严格二叉树,总节点数满足公式 $2n - 1$(其中 $n$ 为叶子节点数量)。
当 $n = 19$ 时,总节点数为 $2 \times 19 - 1 = 37$。
因此该树恰好有 37 个节点,选项 (B) 正确。
35
考虑一个包含 7 个节点的完全二叉树。设 A 表示从根节点开始进行广度优先搜索(BFS)时前 3 个元素的集合,B 表示从根节点开始进行深度优先搜索(DFS)时前 3 个元素的集合。|A−B|的值是( )。
在 BFS 情况下,如果绘制完全二叉树,则集合 A 包含第 1 层和第 2 层的所有元素。在 DFS 情况下,集合 B 包含第 1 层、第 2 层和第 3 层的部分元素。
- 第 1 层 = 1 个元素
- 第 2 层 = 2 个元素
- 第 3 层 = 4 个元素
集合 A 构成:
- 第 1 层:第一个元素
- 第 2 层:第二个和第三个元素
集合 B 构成:
- 第 1 层:1 个元素
- 第 2 层:第二个元素
- 第 3 层:第三个元素
交集关系:
- A ∩ B 包含 L1 的 1 个元素与 L2 的 1 个元素
- A - B 包含 L2 剩余的 1 个元素
因此 |A - B| = 1
36
以下哪项序列表示给定树的后序遍历序列?( )
- a 是根节点,左子节点是 b,右子节点是 e;
- b 的左子节点是 c,右子节点是 d;
- e 的左子节点是 f;
- c 的左子节点是 g。
- 后序遍历规则:左子树 → 右子树 → 根节点
- 从最左下角的叶子节点 g 开始遍历:
- 遍历 g → 回溯到父节点 c → 遍历 c 的右子树 d → 最终输出 g c d
- 返回到 b 节点时,已遍历完其左右子树,输出 b
- 继续向上回溯到 a 节点,此时需先处理 a 的右子树:
- 右子树根节点 e 的左子树 f 先被遍历 → 输出 f e
- 最后输出根节点 a,完整序列为 g c d b f e a
- 对比选项发现 C 与推导结果一致
37
某二叉树的中序遍历和先序遍历结果分别为 d b e a f c g 和 a b d e c f g。该二叉树的后序遍历结果为( ):
解析:
- 根据先序遍历首元素确定根节点为
a
- 中序遍历
d b e a f c g
可划分为左子树d b e
和右子树f c g
- 递归构建左右子树后,后序遍历顺序应为:
- 左子树后序:
d e b
- 右子树后序:
f g c
- 最终结果:
d e b f g c a
- 左子树后序:
选项 (C) 是正确答案。
38
考虑一个 $n$ 元素二叉堆的数组表示形式,其中元素存储在数组的索引 $1$ 到索引 $n$ 之间。对于存储在数组索引 $i$ 处的元素($i \le n$),其父节点的索引是( ):
解析:
- 在二叉堆的数组表示中,每个节点的父节点索引通过
floor(i/2)
计算得到。 - 例如,当 i=2 或 i=3 时,父节点均为索引 1;当 i=4、5 时,父节点为索引 2。
- 因此,正确答案是 B。
39
在对大小为 n 的已排序数组进行递归二分查找时,最坏情况下执行的算术运算次数是多少?( )
解析
- 算法特性:二分查找每次迭代通过
(low + high) // 2
计算中间索引,涉及一次算术运算。 - 递归深度:由于每次将搜索区间减半,递归调用最多执行
log₂(n)
层。 - 结论:总运算次数与递归深度成正比,即 Θ(log₂(n)),对应选项 B。
40
考虑一个包含 $n$ 个节点的二叉树,其中每个节点最多有两个子节点。树的高度定义为从根节点到任意叶节点的最大边数。关于该二叉树的高度 $h$,以下哪项陈述是正确的?
( )
在二叉树中,高度 h 表示从根节点到任意叶节点的最长路径。
最坏情况分析:
- 当每个父节点只有一个子节点(左或右)而另一个子节点为空时,树的高度将小于 n-1
- 完全二叉树等平衡二叉树包含最大节点数时,其高度可以等于 n-1
结论:
- 树的高度 可能大于或等于 n-1(取决于具体结构)
- 选项 b) 正确描述了这一特性,因为树的高度会随着节点排列方式的不同而变化
- 其他选项均存在绝对化表述(“总是”),与实际情况不符
41
大小平衡二叉树是一种二叉树,其中每个节点的左右子树的节点数之差最多为 1。一个节点到根节点的距离是从根节点到该节点的路径长度。二叉树的高度是任意叶子节点到根节点的最大距离。
a. 使用对 $ h $ 的归纳法证明:高度为 $ h $ 的大小平衡二叉树至少包含 $ 2^h $ 个节点。
b. 在高度 $ h \leq 1 $ 的大小平衡二叉树中,距离根节点 $ h-1 $ 的节点有多少个?( )仅写出答案,无需任何解释。
42
初始包含 $n$ 个元素的 AVL 树中插入 $n^2$ 个元素时,最坏情况下的时间复杂度是多少?( )
由于 AVL 树是平衡树,其高度为 $ O(\log n) $。因此,在 AVL 树中最坏情况下插入一个元素的时间复杂度为 $ O(\log n) $。注意:每次插入操作包括以下步骤:
- 查找插入位置 = $ O(\log n) $
- 若插入后性质不满足,则进行旋转调整 = $ O(\log n) $
因此,AVL 树的单次插入操作总时间为 $ O(\log n) + O(\log n) = O(\log n) $。
现在需要插入 $ n^2 $ 个元素,因此总时间复杂度为 $ O(n^2 \log n) $。
另一种方法:
最坏情况下,第 1 次插入时间复杂度 = $ O(\log n) $
第 2 次插入时间复杂度 = $ O(\log(n+1)) $
…
第 $ n^2 $ 次插入时间复杂度 = $ O(\log(n + n^2)) $
总时间复杂度为:
$$ \begin{align*} \text{时间复杂度} &= O(\log n) + O(\log(n+1)) + \dots + O(\log(n + n^2)) \\ &= O(\log [n(n+1)(n+2)\dots(n+n^2)]) \\ &= O(\log n^{n^2}) \\ &= O(n^2 \log n) \end{align*} $$
选项 (C) 正确。
43
在包含 $n \cdot 2^n$ 个元素的平衡二叉搜索树中,最坏情况下查找一个元素的时间复杂度是( )
查找元素所需时间为 $\Theta(h)$,其中 $h$ 是二叉搜索树(BST)的高度。平衡 BST 的高度随节点数量呈对数增长。因此最坏情况下的查找时间应为: $$ \Theta\left( \log(n \cdot 2^n) \right) = \Theta\left( \log n + \log(2^n) \right) = \Theta\left( \log n + n \right) = \Theta(n) $$
44
任何包含 7 个节点的 AVL 树的最大高度是多少?(假设单个节点的树的高度为 0。)
AVL 树是二叉树,具有以下限制:
- 子节点的高度差最多为 1。
- 两个子树都是 AVL 树。
以下是使用 7 个节点可以得到的最不平衡的 AVL 树:
a
/ \
b c
/ \ /
d e g
\
h
AVL 树的平衡性要求任意节点左右子树高度差不超过 1。当构造最不平衡的合法 AVL 树时,每个节点都尽可能让左右子树高度差为 1。对于 7 个节点的情况,这种递归构造会形成高度为 3 的树(根节点 a → b/c → d/e/g → h)。若继续增加高度会导致违反平衡约束,因此最大高度为 3。
45
考虑以下 AVL 树。
60
/ \
20 100
/ \
80 120
在插入 70 后,下列哪一个是更新后的 AVL 树?( )
70
/ \
60 100
/ / \
20 80 120
100
/ \
60 120
/ \ /
20 70 80
80
/ \
60 100
/ \ \
20 70 120
80
/ \
60 100
/ / \
20 70 120
参考以下步骤了解 AVL 插入过程。AVL 树 | 插入部分(第一部分)
平衡过程解析
初始插入状态
插入 70 后,树变为:60 / \ 20 100 / \ 80 120 / 70
失衡检测
- 从插入节点向上回溯至根节点
- 在节点
60
处发现失衡(右左情况)
旋转操作
- 第一步:右旋 100
60 60 / \ / \ 20 100 20 80 / \ / / \ 80 120 70 100 120 / 70
- 第二步:左旋 60
60 80 / \ / \ 20 80 60 100 / \ / \ \ 70 100 20 70 120 \ 120
- 第一步:右旋 100
最终平衡结果
平衡后的 AVL 树结构与选项 C 完全一致:80 / \ 60 100 / \ \ 20 70 120
46
以下哪项是正确的?( )
解析
- 红黑树高度特性:包含 n 个节点的红黑树高度 ≤ 2Log₂(n+1)
- AVL 树高度特性:包含 n 个节点的 AVL 树高度 < Logφ(√5(n+2)) - 2
- 平衡性对比:AVL 树比红黑树更平衡
- 旋转操作差异:AVL 树在插入/删除时可能需要更多旋转
47
以下关于 AVL 树和红黑树的说法中,哪一项是正确的?( )
解析:
AVL 树插入操作
需要两次遍历路径:- 第一次从根节点到插入位置完成节点插入;
- 第二次从插入节点向上回溯至根节点,检测并修复平衡性(通过旋转操作)。
红黑树插入操作
仅需一次遍历路径:
插入完成后,通过自顶向下的修复策略(如变色、旋转)直接沿插入路径向上调整,无需额外回溯。
因此,选项 A 正确,其余选项均存在描述错误。
48
给定两个平衡二叉搜索树 B1(包含 n 个元素)和 B2(包含 m 个元素),将它们合并为另一个包含 m+n 个元素的平衡二叉树,已知最佳算法的时间复杂度是多少?( )
实现步骤
- 对 B1 和 B2 分别进行中序遍历,得到两个有序数组 A1(长度 n)和 A2(长度 m)
- 使用双指针法在 O(m+n) 时间内合并两个有序数组
- 对合并后的有序数组递归构建平衡二叉树:
- 取中间元素作为根节点
- 左子数组递归构建左子树
- 右子数组递归构建右子树
时间复杂度分析
- 中序遍历:O(n + m)
- 数组合并:O(n + m)
- 构建新树:O(n + m)
- 总体时间复杂度为三者之和 → O(n + m)
关键结论
平衡二叉树的性质保证了中序遍历结果严格有序,而线性时间的数组合并与树构建操作共同决定了最优解的时间复杂度上限。
49
需要一种数据结构来存储一组整数,使得以下每种操作都可以在 O(log n) 时间内完成(其中 n 是集合中元素的数量):
I. 删除最小元素
II. 如果元素不在集合中则插入该元素
以下哪种数据结构可以用于此目的?( )
解析:
平衡二叉搜索树特性
- 支持在 $O(\log n)$ 最坏情况时间复杂度下完成查找、插入和删除操作
- 最小元素可通过遍历左子树快速定位(左下角节点)
堆的局限性
- 虽然支持 $O(\log n)$ 的插入与删除最小值操作
- 关键缺陷:无法在 $O(\log n)$ 时间内判断元素是否存在
- 题目要求的"仅当元素不存在时插入"操作需要先执行存在性查询,而堆不支持高效查询
结论对比
数据结构 | 查找/插入/删除 | 判断存在性 | 满足题目需求 |
---|---|---|---|
平衡二叉搜索树 | ✅ $O(\log n)$ | ✅ $O(\log n)$ | ✅ |
堆 | ✅ $O(\log n)$ | ❌ 无法实现 | ❌ |
因此,选项 (B) 正确。
50
假设数字序列 7, 5, 1, 8, 3, 6, 0, 9, 4, 2 按此顺序插入一个初始为空的二叉搜索树中。该二叉搜索树使用自然数的逆序比较规则(即 9 被视为最小值,0 被视为最大值)。最终生成的二叉搜索树的中序遍历结果是( ):
D
二叉搜索树的中序遍历始终会按升序输出键值。
本题中采用的是自然数的逆序比较规则(即 9 是最小值,0 是最大值)。
因此升序排列应为 9, 8, 7, 6, 5, 4, 3, 2, 1, 0。
所以选项 (D) 正确。
51
以下哪项是正确的?( )
- AVL 树特性:AVL 树是一种自平衡二叉搜索树,通过旋转操作维持高度平衡
- 时间复杂度对比:
- AVL 树搜索时间复杂度始终为 θ(log n)
- 普通二叉搜索树在极端情况下(如完全倾斜)会退化为链表,此时搜索时间复杂度为 θ(n)
- 关键区别:平衡性保障了 AVL 树的最坏情况性能,而普通二叉搜索树的性能依赖于输入数据的分布
52
以下哪一个是 AVL 树?( )
100
/ \
50 200
/ \
10 300
100
/ \
50 200
/ / \
10 150 300
/
5
100
/ \
50 200
/ \ / \
10 60 150 300
/ \ \
5 180 400
- 定义:若二叉搜索树中每个节点的平衡因子均为 -1、0 或 1,则该树为 AVL 树。
- 节点 X 的平衡因子定义为:
左子树高度 - 右子树高度
- 节点 X 的平衡因子定义为:
- 分析:
- 在树 B 中,值为 50 的节点平衡因子为 2(左子树高度为 2,右子树高度为 0),因此 B 不是 AVL 树。
- 其他选项中的树均满足所有节点平衡因子在 [-1, 1] 范围内。
8 - 图
1
一个简单图的度数序列是将图中所有节点的度数按降序排列得到的序列。以下哪些序列不可能是任何图的度数序列?
I. 7, 6, 5, 4, 4, 3, 2, 1
II. 6, 6, 6, 6, 3, 3, 2, 2
III. 7, 6, 6, 4, 4, 3, 2, 2
IV. 8, 7, 7, 6, 4, 2, 1, 1
解释:
- II (6,6,6,6,3,3,2,2):所有顶点度数之和为 $6+6+6+6+3+3+2+2 = 34$,是奇数,违反了图论中“图的度数和必须为偶数”的基本性质。
- IV (8,7,7,6,4,2,1,1):包含一个度数为 8 的顶点,但整个图仅有 8 个顶点,这意味着该顶点需与其余 7 个顶点相连。然而序列中存在度数为 1 的顶点,其只能与一个顶点相连,无法满足与度数为 8 的顶点的连接需求。
2
考虑一个无向无权图 $G$。从节点 $r$ 开始进行广度优先遍历。设 $d(r, u)$ 和 $d(r, v)$ 分别为图 $G$ 中从 $r$ 到 $u$ 和 $v$ 的最短路径长度。如果在广度优先遍历中 $u$ 比 $v$ 先被访问,以下哪个陈述是正确的?( )
- 当 u 和 v 位于同一 BFS 层时,它们的最短路径长度相等,即 $d(r, u) = d(r, v)$
- 当 u 比 v 更早被访问时,若不在同一层,则 $d(r, u) < d(r, v)$
3
给定一个包含 $n$ 个顶点的集合 $ V = {V₁, V₂, \ldots, Vₙ} $,可以构造出多少个无向图(不一定是连通的)?
( )
解释:
- 在无向图中,顶点集合大小为 $ n $ 时,最多可构造 $ \frac{n(n-1)}{2} $ 条边。
- 对于每一条可能的边,存在两种状态:存在 或 不存在。
- 因此,所有可能的无向图数量等于从 $ \frac{n(n-1)}{2} $ 条边中进行二元选择的组合数: $$ 2^{\frac{n(n-1)}{2}} $$
4
以下关于无向图的陈述中,哪些是正确的?
P: 奇度顶点的数量是偶数
Q: 所有顶点的度数之和是偶数
解析
- P 的正确性:在无向图中,添加一条边会使两个顶点的度数各增加 1。由于每次操作都改变两个顶点的奇偶性,奇度顶点的总数始终为偶数(如从偶数变为偶数 +2 或偶数 -2)。
- Q 的正确性:每条边对总度数的贡献为 2(连接两个顶点),因此所有顶点的度数之和恒等于 $2 \times$ 边数,必然是偶数。即使存在奇数度数的顶点,其总和仍会被偶数个奇数相加抵消(如 $3 + 5 = 8$)。
5
在以下哪种情况下,有向无环图(DAG)最为适用?( )
有向无环图(DAG)的核心特性
- 拓扑排序支持:通过无环结构实现任务顺序排列
- 依赖管理优势:天然适合表达任务间的先后约束关系
- 调度优化能力:可有效进行资源分配与进度规划
DAG 的这些特性使其成为项目管理工具(如甘特图、PERT 图)的基础数据结构,能避免循环依赖导致的逻辑矛盾。
6
在一个具有 $n$ 个顶点的无环无向图中,最多有多少条边?( )
解析:
- 有环图:最大边数为 $ \frac{n(n-1)}{2} $
- 无环图:边数最多的无环图是一个生成树,此时边数为 $ n-1 $
因此,正确答案是 $ n-1 $ 条边。
7
在有向图中,汇点是指一个顶点 i
满足:所有其他顶点 j
≠ i
都有一条边指向 i
,并且 i
没有任何出边。一个包含 n
个顶点的有向图 G
通过邻接矩阵 A
表示,其中 A[i][j] = 1
表示存在从顶点 i
到 j
的边,否则为 0
。以下算法用于判断图 G
中是否存在汇点。
i = 0;
do {
j = i + 1;
while ((j < n) && E1) j++;
if (j < n) E2;
} while (j < n);
flag = 1;
for (j = 0; j < n; j++)
if ((j != i) && E3)
flag = 0;
if (flag)
printf("Sink exists");
else
printf("Sink does not exist");
选择正确的 E3 表达式( ):
以下解释是针对本题前一部分的内容:
要使顶点 i
成为汇点,必须不存在从 i
到任何其他顶点的边。根据题目给出的输入条件,A[i][j]=1
表示存在从 i
到 j
的边,A[i][j]=0
表示不存在该边。对于 i
成为汇点,需要满足:对所有 j
,A[i][j]=0
(无出边),且对所有 j
,A[j][i]=1
(所有其他顶点都指向 i
)。上述伪代码从 i=0
开始检查每个顶点是否为汇点。它实际上会检查 i
之后的所有顶点 j
是否可能成为汇点。该算法的巧妙之处在于不检查 j
小于 i
的情况。循环选出的 i
可能不是汇点,但确保不会忽略潜在的汇点。最终在 do-while
循环后会验证 i
是否真正为汇点。
当 A[i][j]
为 0
时,i
是潜在汇点。因此 E1
应为!A[i][j]
。若此条件不成立,则 i
不是汇点。所有 j<i
也不能是汇点,因为不存在从 i
到 j
的边。此时下一个潜在汇点可能是 j
。所以 E2
应为 i=j
。
关于本题的解释:
以下伪代码验证上述选出的潜在汇点是否确实为汇点。
flag = 1;
for (j = 0; j < n; j++)
if ((j != i) && E3)
flag = 0;
flag=0
表示 i
不是汇点。一旦发现 i
不符合汇点条件,立即设置 flag=0
。若存在任意 j≠i
使得以下任一条件成立,则 i
不是汇点:
- 出边存在:
A[i][j] = 1
(存在从 i 出发的边) - 未被指向:
A[j][i] = 0
(未被其他顶点指向)
因此 E3 应为 (A[i][j] || !A[j][i])
。选项 D 正确。
8
对于下文所示的无向带权图,以下哪一序列的边表示使用 Prim 算法构造最小生成树的正确执行过程?

在 Prim 算法中,我们从任意一个节点开始,不断探索已包含节点的最小成本邻居。具体执行过程如下:
- 初始化:选择任意起始节点(如 d),将其加入生成树集合。
- 迭代扩展:每次从未选边中选择连接已选节点与未选节点的最小权重边。
- 终止条件:当所有节点均被包含时停止。
选项分析:
- 正确选项需满足每一步都选择当前可扩展的最小权重边。
- 选项 C 的边序列
(d, f), (f, c), (d, a), (a, b), (c, e), (f, h), (g, h), (g, i)
符合 Prim 算法的贪心策略。 - 其他选项存在提前选择非最小权重边或跳过必要连接的情况。
9
考虑一个具有 $n$ 个顶点和 $m$ 条边的有向图,其中所有边的权重相同。求该图最小生成树的最佳已知算法的时间复杂度( )?
当图中所有边的权重相等时,最小生成树的构造可通过以下方式实现:
- 深度优先搜索(DFS):通过遍历图构建深度优先树,该树即为有效生成树
- 时间复杂度分析:DFS 的时间复杂度由访问所有顶点(n)和边(m)决定,因此总时间为 O(m+n)
- 结论:此方法在等权图中可直接获得最优解,无需比较权重排序
10
设 G 是一个具有 20 个顶点和 8 个连通分量的简单图。如果我们从 G 中删除一个顶点,那么 G 中的连通分量数量应该介于( )之间。
解析
- 情况 1:如果删除的是 G 中的孤立顶点(即该顶点本身构成一个连通分量),则 G 的连通分量数会减少为 7。
- 情况 2:如果 G 是星型图,则删除其关节点后会得到 19 个连通分量。
因此,G 的连通分量数量应介于 7 到 19 之间。
11
设图 $G$ 有 100 个顶点,编号为 1 到 100。若两个顶点 $i$ 和 $j$ 满足 $|i−j|=8$ 或 $|i−j|=12$,则它们相邻。图 $G$ 中的连通分量数目是( )
当顶点按差值为 12 连接时,连通分量数减少至 4:
- 第一列(1-4)与第五列(9-12)相连
- 第二列(5-8)与第六列(13-16)相连
- 第三列(17-20)与第七列(25-28)相连
- 第四列(21-24)与第八列(29-32)相连
由于差值为 8 的连接关系已被差值为 12 的连接覆盖,所有顶点最终归并为 4 个独立连通分量。因此选项 (B) 正确。
12
给定一个顶点集合 {0, 1, …, n−1} 上的任意固定 n 值的完全无向带权图 G。请画出 G 的最小生成树,当:
a) 边 (u,v) 的权重为 |u−v|
b) 边 (u,v) 的权重为 u + v
13
以下哪种数据结构在通过广度优先搜索遍历给定图时非常有用?( )
解析:
- 广度优先搜索(BFS)执行的是层级遍历
- 使用队列可以很好地实现这一特性
- 队列采用先进先出(FIFO)的顺序
- 先入队的节点会首先被探索,从而保持遍历的顺序
14
在下图中,深度优先搜索(DFS)的发现时间戳和完成时间戳以 x/y 的形式表示,其中 x 是发现时间戳,y 是完成时间戳。它显示了以下哪一个深度优先森林?( )
解析: 根据 DFS 的时间戳规则:
- 发现时间戳 按递增顺序记录首次访问节点的顺序
- 完成时间戳 按递减顺序记录回溯时的节点完成顺序
选项 A 的划分符合以下访问路径:
- 从起点
a
出发,依次访问b → e
- 回溯至
a
后继续访问c → d → h
- 最终访问剩余分支
f → g
该顺序与时间戳的递增/递减规律完全匹配,因此选项 A 正确。
解释
根据 DFS 时间戳规则:
- 发现时间戳较小的节点先被访问
- 完成时间戳较大的节点最后被回溯
选项 A 中的时间戳顺序符合 DFS 遍历的特性。
其他选项均存在时间戳矛盾(如子树完成时间早于父节点发现时间等)。
15
以下哪项是邻接表表示法相对于邻接矩阵表示法在图中的优势?( )
解释:
邻接表表示法具有以下优势:
- 空间效率更高:特别适合稀疏图,节省存储空间。
- 图遍历算法时间复杂度更低:DFS 和 BFS 在邻接表中为 $O(V + E)$,而在邻接矩阵中为 $O(V^2)$。
- 添加新顶点更灵活:相较于邻接矩阵,邻接表更容易扩展顶点数量。
16
考虑下图所示的有向图。从顶点 S 到 T 存在多条最短路径。Dijkstra 最短路径算法会报告哪一条?假设在任何迭代中,只有当发现更短的路径时,顶点 v 的最短路径才会被更新。( )

Dijkstra 算法执行流程解析:
- 当算法处理到顶点
C
时,其邻接顶点D
和E
的临时距离分别被计算为 7 和 6 - 此时优先级队列中最小距离顶点为
E
(距离值 6 < 7) - 根据算法规则,下一步将选择距离最小的顶点
E
进行松弛操作 - 最终通过路径
S → A → C → E → T
构成最短路径 SACET
关键结论:由于 Dijkstra 算法始终选择当前已知最短距离的顶点进行扩展,因此在 C
节点处理阶段会选择距离更小的 E
节点继续搜索
17
在无权图上实现 Dijkstra 最短路径算法以使其运行时间为线性时间时,应使用的数据结构是( ):
在无权图中,最短路径意味着需要遍历的边数最少。 这与所有权重恰好为 1 的加权版本问题相同。如果我们使用 队列(先进先出)而非优先队列(最小堆),则可以在线性时间 $O(|V| + |E|)$ 内得到最短路径。本质上我们通过图的 广度优先搜索(BFS)遍历来获取最短路径。
18
从下图的顶点 a 运行 Dijkstra 单源最短路径算法时,计算出正确的最短路径距离到( ):
Dijkstra 单源最短路径算法不能保证在包含负权边的图中正确工作,但该算法在给定图中有效。
让我们逐步验证:
- 第一次遍历后
- b: 1
- b 是最小值,因此 b 的最短距离为 1。
- 第二次遍历后
- c: 3,e: -2
- e 是最小值,因此 e 的最短距离为 -2
- 第三次遍历后
- c: 3,f: 0
- f 是最小值,因此 f 的最短距离为 0
- 第四次遍历后
- c: 3,g: 3
- 两者相同,选择 g,因此 g 的最短距离为 3
- 第五次遍历后
- c: 3,h: 5
- c 是最小值,因此 c 的最短距离为 3
- 第六次遍历后
- h: -2
- h 是最小值,因此 h 的最短距离为 -2
注意:虽然最终所有顶点都被处理,但由于图中存在负权边(如 e→h 的 -4),Dijkstra 算法理论上无法保证正确性。但在本例中,由于负权边未形成负权环且起点 a 到相关顶点的路径权重始终递增,算法仍能得出正确结果。
19
在一个无权、无向的连通图中,从节点 S 到其他所有节点的最短路径,按时间复杂度而言最高效的是以下哪种方法?( )
Dijkstra 算法
- 时间复杂度:O(|V|² + |E|)
- 适用于有权图
Warshall 算法
- 时间复杂度:O(|V|³)
- 通常用于传递闭包,而非最短路径
DFS
- 不适用于寻找最短路径
BFS
- 时间复杂度:O(|E| + |V|)
- 适用于无权图,能有效找到最短路径
20
假设我们以顶点 P 为源点,在以下边权有向图上运行 Dijkstra 单源最短路径算法。各节点按照什么顺序被加入到最短路径距离已确定的顶点集合中?
答案是 (B)。在 Dijkstra 算法中,每一步我们都会从当前已找到最短路径的顶点集合中,选择一个与其距离最短的顶点,并将连接这两个顶点的边加入到最短路径中。
21
Bellman-Ford 单源最短路径算法在 n 个顶点的完全图上的时间复杂度是多少?
解析:
- Bellman Ford 算法的基本时间复杂度为 Θ(VE)
- V 表示顶点数量
- E 表示边的数量
- 在完全图中,边数 E 满足 E = Θ(V²)
- 代入计算得最终时间复杂度: $$ Θ(V \times V^2) = Θ(V^3) $$
- 因此选项 C 是正确答案。
22
在带权图中,假设使用最短路径算法正确计算了从源点 s 到目标点 t 的最短路径。以下陈述是否成立?( )
如果我们将每条边的权重增加 1,则最短路径始终不变。
请看以下反例说明:
- 原始图结构
四条边及对应权重:- s-a: 1
- a-b: 1
- b-t: 1
- s-t: 4
1 1 1
s-----a-----b-----t
\ /
\ /
\___________/
4
修改前最短路径
路径s-a → a-b → b-t
总权重 = 1 + 1 + 1 = 3修改后权重变化
所有边权重 +1 后:- s-a: 2
- a-b: 2
- b-t: 2
- s-t: 5
修改后路径比较
- 原路径总权重 = 2 + 2 + 2 = 6
- 新路径
s-t
总权重 = 5
结论
修改后原最短路径不再是全局最优解,说明"最短路径始终不变"的陈述不成立。
23
在有向无环图(DAG)中,以下哪种算法可以高效地计算单源最短路径?( )
解析
- 核心原理:拓扑排序利用 DAG 的特性,按拓扑序依次松弛每条边,确保每个节点的最短路径被唯一确定。
- 时间复杂度:$O(V + E)$,这是目前已知针对 DAG 的最优解法。
- 对比其他选项:
- Dijkstra 算法需依赖优先队列,时间复杂度通常为 $O(E \log V)$ 或更高。
- Bellman Ford 算法虽可处理负权边,但时间复杂度为 $O(VE)$,效率较低。
- 强连通分量与最短路径问题无直接关联。
- 应用场景:特别适合任务调度、数据流分析等 DAG 相关问题。
24
关于最短路径,以下陈述是否有效?给定一个图,假设我们已经计算出从源点到所有其他顶点的最短路径。如果我们将图中所有边的权重修改为原始权重的两倍,则最短路径保持不变,仅路径的总权重发生变化。( )
25
将以下内容进行匹配
组 A | 组 B |
---|---|
a) Dijkstra 单源最短路径算法 | p) 动态规划 |
b) Bellman Ford 单源最短路径算法 | q) 回溯法 |
c) Floyd Warshall 所有点对最短路径算法 | r) 贪心算法 |
- Dijkstra 算法:采用贪心算法,通过每次选择未确定最短路径的顶点中距离最小的节点进行扩展。
- Bellman Ford 算法:基于动态规划,通过松弛操作逐步更新最短路径估计值。
- Floyd Warshall 算法:同样属于动态规划,利用矩阵迭代计算所有点对之间的最短路径。
26
以下陈述是否有效?在所有边权重均唯一的加权图中(任意两条边的权重不同),从源点到目标点的最短路径总是唯一的( )。
- 一条单边权重为 5 的路径
- 另一条由权重 2 和 3 组成的双边路径
它们的总权重相同(2 + 3 = 5)
27
以下陈述是否有效?给定一个所有边权均为正的图,Dijkstra 算法和 Bellman-Ford 算法生成的最短路径可能不同( ),但路径权重始终相同。
- Dijkstra 算法:基于贪心策略,每次选择当前距离最小的节点进行松弛操作,优先保证局部最优解。
- Bellman-Ford 算法:通过动态规划迭代所有边,逐步更新最短路径估计值,允许全局调整。
- 结论:两种算法虽然计算路径的方式不同,但在无负权边的情况下,最终得到的最短路径权重必然一致。但由于路径选择策略差异,实际经过的边可能不同。
28
以下关于 Bellman-Ford 最短路径算法的陈述中,哪些是正确的?
P: 如果存在负权环,则总是能检测到。
Q: 检测是否存在从源点可达的负权环。
Bellman-Ford 最短路径算法是单源最短路径算法。因此它只能检测从给定源点可达的负权环,而非图中所有可能存在的负权环。考虑一个不连通图,其中某个负权环完全无法从源点到达的情况。
如果存在负权环,则在最短路径计算中会体现出来——因为每次经过该环都会使路径长度不断减小。
29
设 $G(V, E)$ 是一个具有正边权的无向图。使用二进制堆数据结构实现 Dijkstra 单源最短路径算法的时间复杂度为( ):
在使用**二进制堆(Binary Heap)**实现的 Dijkstra 算法中,时间复杂度主要来自两部分操作:
- 取出当前距离最小的顶点:最多执行 $|V|$ 次,每次操作代价是 $\log|V|$,总共是 $O(|V| \log|V|)$。
- 更新邻接点的距离:最多执行 $|E|$ 次,每次操作代价是 $\log|V|$,总共是 $O(|E| \log|V|)$。
所以总时间复杂度为:
$$O(|V| \log|V| + |E| \log|V|) = O((|E| + |V|) \log|V|)$$
30
设 $G(V, E)$ 是一个包含 $n$ 个顶点的有向图。$G$ 中从 $v_i$ 到 $v_j$ 的路径是顶点序列 $(v_i, v_{i+1}, \cdots, v_j)$,满足对所有 $k \in [i, j-1]$,$(v_k, v_{k+1}) \in E$。简单路径是指路径中不包含重复顶点的路径。
设 A 为一个 n×n 数组,初始化如下。考虑以下算法:
for i = 1 to n
for j = 1 to n
for k = 1 to n
A[j, k] = max(A[j, k], A[j, i] + A[i, k]);
在上述算法执行结束后,以下哪项陈述对于所有 j 和 k 必然成立?( )
解析
在原始输入矩阵中,若存在从 j
到 k
的边则 A[j, k]=1
,否则为 0。
关键分析步骤:
算法本质
表达式A[j, k] = max(A[j, k], A[j, i] + A[i, k])
实际上是在计算从 j 到 k 的路径中经过中间节点 i 的最大边数。每次迭代时,算法尝试通过新增的中间节点 i 来延长已知路径。数值含义
-
初始状态:A[j,k]
表示是否存在直接边(1
或0
)-
迭代过程:A[j,k]
最终表示从j
到k
的最长路径所含的边数-
上界约束:由于简单路径不能重复顶点,最长路径至多包含n-1
条边结论推导
-
当A[j,k] = n-1
时,说明存在一条长度为n-1
的简单路径(覆盖所有顶点)-
若A[j,k] ≥ n
,则必然存在环(因为简单路径最多n-1
条边)-
因此 D 选项表述了"
简单路径最多包含A[j,k]
条边"
这一必然成立的关系错误选项排除
- A 错误:当存在环时
A[j,k]
可能超过 n - B 错误:
A[j,k] ≥ n-1
仅说明存在长度为 n-1 的路径,但不足以证明存在哈密顿回路 - C 错误:算法求的是最长路径的边数,而非路径长度(权重总和)
- A 错误:当存在环时
31
设 $G = (V, E)$ 是一个简单无向图,$s$ 是其中的一个特定顶点(称为源点)。对于 $x \in V$,用 $d(x)$ 表示从 $s$ 到 $x$ 在 $G$ 中的最短距离。从 $s$ 开始执行广度优先搜索(BFS),得到一棵 BFS 树 $T$。若 $(u, v)$ 是 $G$ 的一条不在 $T$ 中的边,则以下哪一项不可能是 $d(u)-d(v)$ 的值?( )
注意:给定的图是无向的,因此边 (u, v) 也意味着 (v, u) 是一条边。
解释:
在无向图中,BFS 构建的最短路径树具有以下性质:
- 非树边的特性:若边 (u, v) 不在 BFS 树 T 中,则 u 和 v 必须属于同一层或相邻层。
- 距离差限制:
- 若 u 和 v 属于同一层(即 d(u) = d(v)),则差值为 0。
- 若 u 和 v 属于相邻层(如 d(u) = d(v) + 1 或 d(v) = d(u) + 1),则差值为 ±1。
- 矛盾性分析:
- 若假设 d(u) – d(v) = 2,则存在更短路径通过边 (u, v) 直接连接 u 和 v,这与 BFS 树定义的最短路径性质矛盾。
因此,在无向图中,非树边两端点的距离差绝对值不可能超过 1。
32
设 $G$ 是一个有向图,其顶点集是 $1$ 到 $100$ 的数字集合。若从顶点 $i$ 到顶点 $j$ 存在边,则当且仅当 $j = i + 1$ 或 $j = 3i$ 时成立。从顶点 $1$ 到顶点 $100$ 的路径中,最少需要多少条边?( )
任务是从顶点 1 到顶点 100 寻找路径中边数最少的方案,其中可以从顶点 i 移动到 i+1 或 $3 \times i$。由于目标是最小化边数,
我们更倾向于选择 $3 \times i$ 的路径。
正向尝试 3 的倍数路径
1 → 3 → 9 → 27 → 81,此时无法继续使用 $3 \times i$ 路径,只能使用 i+1。这种方案会导致较长的路径。反向从终点开始的策略
若数值不是 3 的倍数则减 1,否则除以 3:
100 → 99 → 33 → 11 → 10 → 9 → 3 → 1因此总共需要 7 条边。
33
考虑一个包含 $4$ 个顶点的带权无向图,其中边 ${i, j}$ 的权重由矩阵 $W$ 中的元素 $W_{i,j}$ 给出。使得至少存在某对顶点之间的最短路径中包含权重为 $x$ 的边的最大可能整数值是( )。
设顶点为 0、1、2 和 3。
x 直接连接顶点 2 和 3。不经过 x 的情况下,从 2 到 3 的最短路径权重为 12(路径为 2-1-0-3
)。
当 x 的值大于 12 时,路径 2-3
的直接边权重会超过替代路径 2-1-0-3
的总权重(12),此时最短路径将不再包含权重为 x 的边。因此,x 的最大取值为 12,此时仍满足存在最短路径包含该边的条件。
34
考虑一个具有正边权的带权无向图,设 $uv$ 是图中的一条边。已知从源顶点 $s$ 到 $u$ 的最短路径权重为 $53$,从 $s$ 到 $v$ 的最短路径权重为 $65$。以下哪一项陈述始终成立?( )
根据三角不等性原理:
- 若存在路径 $s \rightarrow \cdots \rightarrow u \rightarrow v$,则满足 $d(s,u) + w(u,v) \geq d(s,v)$
- 已知 $d(s,u)=53$,$d(s,v)=65$
- 代入不等式得:$53 + w(u,v) \geq 65$
- 推导结果:$w(u,v) \geq 12$
35
以下哪种算法用于解决所有节点对的最短路径问题?( )
- Prim 算法:用于求解给定图的最小生成树(MST)。
- Dijkstra 算法:用于在有权图中从源点到其他所有节点的最短路径。
- Bellman-Ford 算法:用于在可能包含负权边的图中求解从源点到其他所有节点的最短距离。
- Floyd-Warshall 算法:它是一种所有节点对的最短路径算法,用于求解每对顶点之间的最短距离。
因此,选项(D)正确。
36
以下哪种数据结构在使用广度优先搜索遍历给定图时非常有用( )?
解析:
队列(先进先出/FIFO)是广度优先搜索(BFS)的核心数据结构。
- BFS 的核心特性是逐层遍历,即从起始节点出发,优先访问当前层级的所有相邻节点后再进入下一层级。
- 队列遵循 FIFO(First In First Out) 原则,能够保证节点按层级顺序依次被访问:
- 先入队的节点会先被处理,其邻接节点随后入队;
- 后入队的节点则等待前序节点处理完成后再处理,从而维持遍历的层次性。
- 相比之下,栈(LIFO)更适合深度优先搜索(DFS),而列表缺乏严格的顺序约束机制。
37
设 $G=(V,E)$ 是一个有向加权图,权重函数为 $w:E→R$。对于某个映射 $f:V→R$,对每条边 (u,v)∈E,定义 $w’(u,v)$=w(u,v)+f(u)−f(v)。以下哪个选项能补全下述句子使其成立?“在 $G$ 中基于 $w$ 的最短路径,在 $w’$ 下也是最短路径,( )”。
A
- 将顶点映射到任意实数值时,最短路径保持不变。
- 任何路径上的中间节点值都会相互抵消,最终仅剩起点和终点的值,其总和即为路径成本。
- 因此最短路径保持一致。
关键分析
- D 选项错误在于"当且仅当"的表述。若改为单纯的"如果"则正确。
- 给出的条件是充分而非必要条件,使得"仅当"部分无效。
- 本质上这意味着所有顶点的势能都为 0(因为它们与新顶点 s 通过零权重边相连)。
- B 和 C 选项也因相同原因错误(正负数限制并非必要条件)。
38
在给定一个所有边权重相同的有向图中,我们可以使用以下哪种方法高效地找到从给定源点到目标点的最短路径?( )
解析
通过广度优先遍历(BFS),我们首先探索距离为一条边的顶点,然后是距离为两条边的所有顶点,依此类推。
39
图的遍历与树的不同之处在于( )。
图的深度优先遍历(DFS)类似于树的深度优先遍历。唯一的区别在于,与树不同,图可能包含循环(节点可能被重复访问)。为了避免对同一节点进行多次处理,需要使用布尔型已访问数组。图可以有多个不同的 DFS 遍历路径。因此选项(A)是正确答案。
40
以下算法适用的数据结构是什么?
- 广度优先搜索
- 深度优先搜索
- Prim 最小生成树
- Kruskal 最小生成树
广度优先搜索使用队列,深度优先搜索使用栈,Prim 最小生成树使用优先队列。Kruskal 最小生成树使用并查集。因此选项 (B) 是正确答案。
41
广度优先搜索算法使用队列数据结构实现。访问以下图中节点的一个可能顺序是( )。
选项 (A) 的顺序为 MNOPQR。由于遍历从 M 开始,但 O 在 N 和 Q 之前被访问,这不符合广度优先搜索的规则(当前深度的所有相邻节点必须在下一层深度的节点前被访问)。选项 (B) 的顺序为 NQMPOR。这里 P 在 O 之前被访问,同样违反了 BFS 规则。选项 (C) 和 (D) 的前缀 QMNP 符合 BFS 特性。观察到 M 在 N 和 P 之前被加入队列(因为 QMNP 中 M 出现在 NP 前),而 R 是 M 的邻居节点,因此会在 N 和 P 的邻居节点(即 O)之前被加入队列。最终 R 会先于 O 被访问。因此,选项 (C) 是正确答案。
42
设 G 为一个无向图。考虑对 G 进行深度优先遍历,得到的深度优先搜索树为 T。假设 u 是 G 中的一个顶点,v 是在遍历中访问完 u 之后第一个被访问的新(未访问过)顶点。以下哪项陈述始终成立?
在 DFS 中,若’v’在’u’之后被访问,则以下两种情况必居其一:
- (u, v) 是一条边。
u
/ \
v w
/ / \
x y z
- ‘u’ 是叶节点。
w
/ \
x v
/ / \
u y z
在 DFS 中,访问完一个节点后,会先递归访问所有未访问过的子节点。如果没有未访问的子节点(u 是叶节点),则控制权返回到父节点,父节点再访问下一个未访问的子节点。因此选项 (C) 是正确答案。
43
以下哪种条件足以检测有向图中的环?
通过深度优先遍历(DFS)技术可以检测有向图中的环。其核心思想是:只有当图中存在回溯边(即某个节点指向其祖先节点)时,才会形成环。为了检测回溯边,需要跟踪两个状态:1)已访问过的节点集合;2)当前递归调用栈中的节点集合(即当前正在访问的路径)。如果在递归过程中遇到一个已经在当前递归栈中的节点,则说明存在环。对于不连通的图,需要先获取 DFS 森林,然后对每棵树单独检查是否存在回溯边。因此选项 (B) 是正确答案。
44
以下陈述是否正确/错误:如果一个有向图的深度优先搜索(DFS)包含回边,那么该图的任何其他深度优先搜索也会至少包含一条回边。
回边表示图中存在环。因此,如果图中存在环,所有深度优先搜索都会至少包含一条回边。
45
Make 是一个工具,它通过读取名为 makefiles 的文件自动从源代码构建可执行程序和库。这些文件指定了如何推导目标程序。以下哪种标准图算法被 Make 使用?
Make 可以通过拓扑排序确定软件的构建顺序。拓扑排序会根据 makefile 中提供的所有依赖关系生成顺序。详情请参见:拓扑排序。因此选项 (B) 是正确答案。
46
在图中给定两个顶点 s 和 t,以下哪种遍历方式(BFS 和 DFS)可以用来判断是否存在从 s 到 t 的路径?
我们可以通过这两种遍历方式来判断是否存在从 s 到 t 的路径。因此选项 (C) 是正确答案。
47
以下陈述是否正确?有向图的深度优先搜索(DFS)总是产生相同数量的树边,即与顶点进行 DFS 的顺序无关。
该陈述是错误的。在有向图中进行深度优先搜索(DFS)时,生成的树边数量可能因顶点考虑顺序的不同而变化。DFS 树中的树边数量取决于 DFS 期间所采取的具体遍历路径。从特定源顶点开始的 DFS 会通过系统化的方式探索邻接顶点,访问邻接顶点的顺序会影响 DFS 树的形成。因此选项 (B) 是正确答案。
48
如果在有向图 G 中,两个顶点 u 和 v 的 DFS 结束时间满足 f[u] > f[v],并且它们属于同一棵 DFS 树(即 DFS 森林中的同一棵树),那么 u 在深度优先树中是 v 的祖先。
49
考虑下图所示的有向无环图(DAG),其中顶点集合 V = {1, 2, 3, 4, 5, 6}。以下哪一项不是该图的拓扑排序?
在选项 D 中,顶点 1 出现在顶点 2 和 3 之后,这违反了拓扑排序的基本规则。根据题目中的 DAG 可以直接观察到,顶点 1 存在指向顶点 2 和 3 的出边,因此顶点 2 和 3 必须出现在顶点 1 之前。显然选项 D 不符合拓扑排序的要求。对于无法直观判断的情况,我们需要掌握如何通过算法确定 DAG 的拓扑排序。综上所述,选项 (D) 是正确答案。
50
考虑从源节点 W 开始的无权、连通、无向图中的广度优先搜索(BFS)遍历的树边。由这些树边构成的树 T 是用于计算以下哪一项的数据结构?
解释:正确答案是 (B) 从 W 到图中每个顶点的最短路径。在无权、连通、无向图中,从源节点 W 进行广度优先搜索(BFS)遍历时,由树边构成的树 T 表示了从 W 到图中其他所有顶点的最短路径。BFS 按层级探索图,从源节点 W 开始,遍历过程中会按照与 W 的距离递增的顺序发现顶点。树 T 中的树边代表了从 W 到每个顶点的最短路径,因为 BFS 确保顶点是按照其与源节点的距离顺序被访问的。选项 (A) 错误,因为树 T 仅表示从 W 出发的最短路径,而非图中任意两顶点间的最短路径。选项 (C) 也不正确,BFS 生成的树 T 包含所有从 W 可达的顶点(而不仅仅是叶子节点),且无论顶点是否为叶子或内部节点,都表示最短路径。选项 (D) 错误,BFS 不提供关于最长路径的信息,它专注于寻找从源节点 W 到其他顶点的最短路径。因此,正确答案是 (B)。
51
假设从某个未知顶点开始对下图执行深度优先搜索。假定仅在首先确认顶点尚未被访问过的情况下才进行递归调用。那么可能的最大递归深度(包括初始调用)是( )。
下图展示了递归树具有最大深度的最坏情况。因此,递归深度为 19(包括第一个节点)。
52
设 T 是无向图 G 的深度优先搜索树。顶点 u 和 n 是该树 T 的叶子节点。u 和 n 在 G 中的度数至少为 2。以下哪项陈述是正确的?
以下示例说明了 A、B、C 都是错误的,所以正确选项为 D。

53
在对具有 n 个顶点的图 G 进行深度优先遍历时,标记了 k 条树边。图 G 中的连通分量数量是:
树边是深度优先搜索树中包含的边。如果一棵树中有 x 条树边,则该树包含 x+1 个顶点。
当图不连通时,DFS 的输出是一片森林。
- 所有顶点都连通的情况。此时 k 必须等于 n-1。根据公式计算得连通分量数为 n-k = n-(n-1)=1
- 没有任何顶点连通的情况。此时 k 必须等于 0。根据公式计算得连通分量数为 n-k = n-0=n
54
考虑以下有向图。该图的顶点具有多少种不同的拓扑排序顺序?注意:此问题以数值型题目形式提出。
以下是六种不同的拓扑排序:
a-b-c-d-e-f
a-d-e-b-c-f
a-b-d-c-e-f
a-d-b-c-e-f
a-b-d-e-c-f
a-d-b-e-c-f
55
设 $G = (V, G)$ 是一个带权无向图,且使用邻接表维护其最小生成树(MST)$T$。若在 $G$ 中新增一条边 $(u, v) \in V×V$,则判断 $T$ 是否仍为新图的 MST 的最坏时间复杂度是:
在一个所有边权重互异的图中,广度优先搜索(BFS)不能直接用于此目的。以下是替代方法:对于包含 V 个顶点的最小生成树(MST),其边数为 V−1 条。遍历邻接表的时间复杂度为 O(V+E),但由于我们处理的是 MST 的 V−1 条边,因此实际遍历时间为 O(V)。题目给出的是 MST 而非完整图,且 MST 中 E=V-1,故时间复杂度 TC = Θ(V + E) = Θ(V + V-1) = Θ(V)。在遍历 MST 的邻接表时,将新添加边的权重与 MST 中现有边的权重进行比较。若新边权重更小,则需更新 MST;否则无需修改。因此该过程的时间复杂度为 O(V)。
56
在一个连通图中,关键点(割点)是指删除该顶点及其关联边后,图会被分割成两个或更多连通分量的顶点。设 T 是通过在连通无向图 G 中进行深度优先搜索(DFS)得到的 DFS 树。以下哪个选项是正确的?
我们逐一验证所有选项:
选项 (A):T 的根节点在 G 中永远不可能是关键点。此选项 错误。观察下图 G 的 DFS 树 T:
r
/ \
a b
当根节点可以成为关键点时,删除与 T 根节点对应的顶点会将图分割为两个连通分量。因此,T 的根节点在 G 中可能成为关键点。
选项 (B):T 的根节点在 G 中是关键点当且仅当它有两个或更多子节点。此选项 正确。根据上述示例及以下树结构:
r
/ \
a b
若 T 的根节点只有一个子节点,则删除其对应顶点不会使图 G 分割为两个或更多连通分量。因此,T 的根节点至少需要有两个子节点才能成为关键点。
选项 (C):T 的叶子节点在 G 中可能是关键点。此选项 错误。如下图 T 所示:
p
/
q
任何叶子节点仅连接到父节点。删除该叶子节点不会导致图 G 分割为多个连通分量。因此,T 的叶子节点不可能是关键点。
选项 (D):如果 u 是 G 中的关键点,并且在 T 中 x 是 u 的祖先,y 是 u 的后代,则 G 中从 x 到 y 的所有路径都必须经过 u。此选项 错误。考虑图 G 的示例:
x - w - v - y
\ /
u u
在通过 DFS 得到的树 T 中,u 是 G 的关键点,x 是 u 的祖先,y 是 u 的后代。尽管满足条件,但存在不经过 u 的路径(如 x→w→v→y)。因此,只有选项 B 正确。
57
考虑一个包含 7 个节点的完全二叉树。设 A 是从根节点开始进行广度优先搜索(BFS)所获得的前 3 个元素的集合,B 是从根节点开始进行深度优先搜索(DFS)所获得的前 3 个元素的集合。|A−B| 的值是( )。
考虑一棵完全二叉树,共有 3 层,总共有 7 个元素。每层的元素数量如下:
- 第 1 层:1 个元素
- 第 2 层:2 个元素
- 第 3 层:4 个元素
广度优先搜索(BFS)
进行 广度优先搜索 时,定义集合 A 包含第 1 层和第 2 层的所有元素:
- 第 1 层:第一个元素
- 第 2 层:第二个和第三个元素
因此:
- A = { 第 1、2、3 个元素 }
深度优先搜索(DFS)
进行 深度优先搜索 时,定义集合 B 包含:
- 第 1 层:第一个元素
- 第 2 层:第二个元素
- 第 3 层:第三个元素
因此:
- B = { 第 1、2、4 个元素 }
集合运算分析
- A ∩ B 包含 1 个元素(第 1 层的根节点)
- A - B 表示 A 中不属于 B 的元素,即 第 2 层中剩下的那个元素
结论
|A - B| = 1
58
对于一个给定的图 $G$,它有 $v$ 个顶点和 $e$ 条边,并且是连通的且没有环路。以下哪项陈述是正确的?
该图 G 是连通且无环路(也称为无循环)的树结构。根据树的定义,其边数 (e) 始终等于顶点数 (v) 减 1,即 e = v - 1 或等价地 v = e + 1。因此选项 (C) 为正确答案。
59
以下哪项陈述是正确的?
P1: 每棵树始终都是图。
P2: 每个图始终都是树。
P3: 每棵树都是图,但并非每个图都是树。
P4: 每个图都是树,但并非每棵树都是图。
9 - 哈希表
1
使用哈希函数 $ h(k) = k \mod 10 $ 和线性探测法,有多少种不同的关键字值插入顺序会导致下图所示的哈希表?( )
position | value |
---|---|
0 | |
1 | |
2 | 42 |
3 | 23 |
4 | 34 |
5 | 52 |
6 | 46 |
7 | 33 |
8 | |
9 |
约束条件:
- 元素
42
、23
和34
必须在52
和33
之前插入。 - 元素
46
必须在33
之前插入。
- 元素
计算方式:
$$ \text{不同序列总数} = 3! \times 5 = 30 $$公式解析:
3!
表示42
、23
和34
可以以任意顺序插入(共 6 种排列)。5
表示46
可以在满足约束的前提下插入到 5 个合法位置中。
2
考虑一个有 100 个槽位的哈希表,冲突通过链地址法解决。假设采用简单均匀哈希,那么在前 3 次插入操作后,第一个 3 个槽位都未被填充的概率是多少?( )
简单均匀哈希函数是一种理想化的哈希函数,它能将元素均匀地分布到哈希表的各个槽位中。此外,每个待哈希元素被分配到任意槽位的概率相等,且与其他元素无关。前 3 次插入后第一个 3 个槽位未被填充的概率为:
- 第一个元素不放入前 3 个槽位的概率:$ \frac{97}{100} $
- 第二个元素不放入前 3 个槽位的概率:$ \frac{97}{100} $
- 第三个元素不放入前 3 个槽位的概率:$ \frac{97}{100} $
综合计算得:
$$
\left(\frac{97}{100}\right) \times \left(\frac{97}{100}\right) \times \left(\frac{97}{100}\right) = \frac{97^3}{100^3}
$$
3
以下哪种整数哈希函数在将键值分布到编号为 0 到 9 的 10 个桶中时,对于 i 从 0 到 2020 的情况,能实现最均匀的分布?( )
由于使用了 mod 10 操作,因此结果的最后一位数字是关键。如果对 0 到 9 的所有数字进行立方运算,得到的结果如下:
数字 | 立方 | 立方的最后一位 |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 8 | 8 |
3 | 27 | 7 |
4 | 64 | 4 |
5 | 125 | 5 |
6 | 216 | 6 |
7 | 343 | 3 |
8 | 512 | 2 |
9 | 729 | 9 |
因此,从 0 到 2020 的所有数字会被均匀地分配到 10 个桶中。如果我们制作平方表,则无法获得均匀分布。在下表中,1、4、6 和 9 重复出现,因此这些桶会有更多条目,而 2、3、7 和 8 则为空。
数字 | 平方 | 平方的最后一位 |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 4 | 4 |
3 | 9 | 9 |
4 | 16 | 6 |
5 | 25 | 5 |
6 | 36 | 6 |
7 | 49 | 9 |
8 | 64 | 4 |
9 | 81 | 1 |
另一种方法是利用幂次循环的概念:
- (a) (0,1,4,9,6,5,6,9,4,1,0) 循环
- (b) (0,1,8,7,4,5,6,3,2,9) 循环
- (c) (0,1,4,9,6,5,6,9,4,1,0) 循环
- (d) (0,2,4,6,8) 循环
因此,只有 h(i) =i³ mod 10 涵盖了 0 到 9 的所有数字。故选项 (C) 正确。
4
以下哪项陈述是正确的?( )
- 哈希函数将任意长度的消息转换为固定长度的代码。
- 哈希函数将固定长度的消息转换为可变长度的代码。
- 不同的消息可能通过哈希函数得到相同的哈希值。
哈希函数被定义为任何可以将任意大小的数据映射到固定大小数据的函数。哈希函数返回的值称为哈希值、哈希码、摘要或简称为哈希:
陈述 1 正确
是的,哈希函数确实可能将不同的值映射到内存中的同一位置,这就是冲突发生的原因,我们有不同技术来处理这个问题:
陈述 3 正确
示例验证:
- 假设哈希函数
h(x) = x % 3
- 验证陈述 1:无论 ‘x’ 的值是什么,h(x) 都会生成一个固定的映射位置(0, 1, 或 2)
- 验证陈述 3:当 x=4 或 x=7 时,h(x)=1,在两种情况下都发生了冲突
结论:
- 陈述 I 和 III 成立
- 陈述 II 错误(哈希函数始终输出固定长度结果)
5
考虑一个均匀分布键值的哈希函数。哈希表大小为 20。在插入多少个键后,新键发生冲突的概率将超过 0.5?( )
每个键发生冲突的概率是 $\frac{1}{20}$(因为总共有 20 个存储位置,且每个键只能映射到一个位置)。设插入 $x$ 个键后概率变为 $\frac{1}{2}$,则有:
$$ \frac{1}{20} \cdot x = \frac{1}{2} \Rightarrow x = 10 $$
因此选项 (D) 是正确答案。
6
当将 n 个键哈希到大小为 m 的哈希表中时,发生冲突的概率是多少?假设哈希函数产生均匀随机分布。( )
- 关键因素:冲突概率主要由两个因素决定:
- 被哈希项的数量 $n$
- 哈希表的大小 $m$
- 变化趋势:
- 当 $n$ 增加时,冲突概率 上升
- 当 $m$ 增大时,冲突概率 下降
- 数学表达式:因此,冲突概率可估计为 $ O\left(\frac{n}{m}\right) $
7
将字符串 K R P C S N Y T J M 的字符插入大小为 10 的哈希表中。使用哈希函数 h(x) = (ord(x) – ord("A") + 1) mod 10
。如果使用线性探测解决冲突,则以下插入会导致冲突( ):
(a) 大小为 10 的哈希表索引范围是 0 到 9。哈希函数为 h(x) = ((ord(x) - ord(A) + 1)) mod 10。对于字符串 K R P C S N Y T J M:
- K 插入索引:(11-1+1) mod 10 = 1
- R 插入索引:(18-1+1) mod 10 = 8
- P 插入索引:(16-1+1) mod 10 = 6
- C 插入索引:(3-1+1) mod 10 = 3
- S 插入索引:(19-1+1) mod 10 = 9
- N 插入索引:(14-1+1) mod 10 = 4
- Y 插入索引:(25-1+1) mod 10 = 5
- T 插入索引:(20-1+1) mod 10 = 0
- J 插入索引:(10-1+1) mod 10 = 0 // 第一次冲突发生
- M 插入索引:(13-1+1) mod 10 = 3 // 第二次冲突发生
仅 J 和 M 导致冲突。
(b) 最终哈希表如下:
0 T
1 K
2 J
3 C
4 N
5 Y
6 P
7 M
8 R
9 S
8
哪种搜索技术在数据查找时的时间复杂度为 O(1)?( )
解析:
哈希表通过键值映射实现常数级时间复杂度 $O(1)$ 的查找操作。
- 二分查找需在有序数组中进行,时间复杂度为 $O(\log n)$
- 线性查找需遍历数据,时间复杂度为 $O(n)$
- AVL 树查找基于平衡二叉树结构,时间复杂度为 $O(\log n)$
哈希函数的理想情况下能直接定位存储位置,因此具有最优的查找效率。
9
考虑一个大小为 $m = 10000$ 的哈希表,其哈希函数为 $ h(K) = \lfloor m (K \cdot A \mod 1) \rfloor $,其中 $ A = \frac{\sqrt{5} - 1}{2} $。键值 123456 被映射到位置( )。
- 给定的哈希函数:h(K) = floor(m (K*A mod 1))
- 其中 $ A = \frac{\sqrt{5} - 1}{2} $
- 计算过程:
$$ \begin{align*} h(123456) &= \left\lfloor 10000 \times \left(123456 \times \frac{\sqrt{5} - 1}{2} \mod 1 \right) \right\rfloor \\ &= \left\lfloor 10000 \times (76300.004115 \mod 1) \right\rfloor \\ &= \left\lfloor 10000 \times 0.004115 \right\rfloor \\ &= \left\lfloor 41.15 \right\rfloor = 41 \end{align*} $$ - 因此,选项 (B) 正确。
10
考虑一个包含 13 个元素的哈希表,其整数键使用哈希函数 f(key) = key mod 13。假设采用线性探测法(linear probing)解决冲突,当按顺序插入键值 661、182、24 和 103 时,键值 103 最终会被插入到哪个位置?( )
- 661 的哈希值:
661 % 13 = 11
→ 插入位置 11 - 182 的哈希值:
182 % 13 = 0
→ 插入位置 0 - 24 的哈希值:
24 % 13 = 11
(冲突)- 线性探测找到下一个可用位置 12
- 103 的哈希值:
103 % 13 = 12
(冲突)- 线性探测找到下一个可用位置 1
- 因此键值 103 最终插入位置为 1,对应选项 (B)
11
使用以下密码函数对明文 ISRO 进行加密时,当密钥 $k = 7$ 时会生成什么密文?[假设 ‘A’ = 0, ‘B’ = 1, …, ‘Z’ = 25]。
$$ C_k(M) = (kM + 13) \mod 26 $$
Ck(M) = (kM + 13) mod 26
此处,‘A’ = 0, ‘B’ = 1, …….‘Z’ = 25
I 的加密过程
- 明文字符 I 对应数值 8
- 代入公式:$7 \times 8 + 13 = 69$
- 取模运算:$69 \mod 26 = 17$
- 数值 17 对应密文字符 R
S 的加密过程
- 明文字符 S 对应数值 18
- 代入公式:$7 \times 18 + 13 = 139$
- 取模运算:$139 \mod 26 = 9$
- 数值 9 对应密文字符 J
R 的加密过程
- 明文字符 R 对应数值 17
- 代入公式:$7 \times 17 + 13 = 132$
- 取模运算:$132 \mod 26 = 2$
- 数值 2 对应密文字符 C
O 的加密过程
- 明文字符 O 对应数值 14
- 代入公式:$7 \times 14 + 13 = 105$
- 取模运算:$105 \mod 26 = 7$
- 数值 7 对应密文字符 H
综上,明文 ISRO 加密后得到密文 RJCH,因此选项 (A) 正确。
12
考虑一个大小为 $ m = 100 $ 的哈希表,其哈希函数为 $ h(k) = \lfloor m(kA \bmod 1) \rfloor $。计算键 $ k = 123456 $ 在哈希表中的存储位置( )。
解析:
哈希函数定义:
$$ h(k) = \lfloor m(kA \bmod 1) \rfloor $$代入已知参数:
- $ m = 100 $
- $ k = 123456 $
- 黄金比例常数 $ A \approx 0.618033 $
计算过程:
- 第一步:
$$ 123456 \times 0.618033 = 76189.882048 $$ - 第二步(取小数部分):
$$ 76189.882048 \bmod 1 = 0.882048 $$ - 第三步(乘以表长):
$$ 100 \times 0.882048 = 88.2048 $$ - 第四步(向下取整):
$$ \lfloor 88.2048 \rfloor = 88 $$
- 第一步:
结论:
键值123456
对应的哈希地址为 88,因此选项 (C) 正确。
13
一个包含 10 个桶的哈希表,每个桶仅有一个槽位,如下图所示。符号 S1 到 S7 最初通过哈希函数和线性探测法插入。在搜索一个不存在的项时,最多需要进行多少次比较?( )
分析思路
情况一:当搜索从索引 0 开始时
- 比较顺序:索引 0 → 索引 1 → 索引 2
- 终止条件:在索引 2 发现空桶
- 总比较次数:3 次
情况二:当搜索从索引 8 开始时
- 比较顺序:索引 8 → 索引 9 → 索引 0 → 索引 1 → 索引 2
- 终止条件:在索引 2 发现空桶
- 总比较次数:5 次
结论
根据线性探测法的特性,搜索路径需连续探测直到遇到空桶。上述两种情况中,从索引 8 开始的搜索路径最长,因此最大比较次数为 5 次。
正确答案为 (B)。
14
一个哈希函数 $ h $ 定义为 $ h(\text{key}) = \text{key} \bmod 7 $,并使用线性探测法解决冲突。将键值 44、45、79、55、91、18、63 插入索引范围为 0 到 6 的哈希表中。键值 18 最终会存储在哪个位置?( )
- 44:$ h(44) = 44 \bmod 7 = 2 $,直接存入位置 2
- 45:$ h(45) = 45 \bmod 7 = 3 $,直接存入位置 3
- 79:$ h(79) = 79 \bmod 7 = 2 $,位置 2 被占用 → 探测位置 3(被占用)→ 存入位置 4
- 55:$ h(55) = 55 \bmod 7 = 6 $,直接存入位置 6
- 91:$ h(91) = 91 \bmod 7 = 0 $,直接存入位置 0
- 18:$ h(18) = 18 \bmod 7 = 4 $,位置 4 被占用 → 探测位置 5(空闲)→ 存入位置 5
- 63:$ h(63) = 63 \bmod 7 = 0 $,位置 0 被占用 → 探测位置 1(空闲)→ 存入位置 1
最终哈希表状态:
索引 | 内容 |
---|---|
0 | 91 |
1 | 63 |
2 | 44 |
3 | 45 |
4 | 79 |
5 | 18 |
6 | 55 |
因此键值 18 存储在位置 5,选项 (C) 正确。
15
考虑一个双重哈希方案,其中主哈希函数为 $h_1(k) = k \% 23$,次哈希函数为 $h_2(k) = 1 + (k \% 19)$。假设表的大小为 $23$。那么对于键值 $k = 90$,在探测序列中第 $1$ 次探测(假设探测序列从第 $0$ 次探测开始)返回的地址是( )。
已知条件
- 表的大小:23
- 键值 k = 90
- 探测次数 i = 1(探测序列中的第 1 次探测)
双重哈希公式
$$ \text{地址} = (h_1(k) + i \times h_2(k)) \mod (\text{表的大小}) $$计算过程
- $ h_1(k) = 90 \mod 23 = 21 $
- $ h_2(k) = 1 + (90 \mod 19) = 15 $
- 代入公式:$ (21 + 15) \mod 23 = 36 \mod 23 = 13 $
结论
选项 (A) 正确。
16
考虑一种针对 4 位整数键的动态哈希方法:
(A) 主哈希表大小为 4。
(B) 使用键的 2 个最低有效位来索引主哈希表。
(C) 初始时,主哈希表条目为空。
(D) 随后,当更多键被哈希到表中时,通过将对应于主哈希表条目的所有键组织成按需增长的二叉树来解决冲突。
(E) 首先,使用第 3 个最低有效位将键划分为左子树和右子树。
(F) 为了进一步解决冲突,每个二叉树节点根据第 4 个最低有效位再细分为左子树和右子树。
(G) 只有在需要时(即发生冲突时)才进行拆分。
考虑以下哈希表状态。
下列哪个键插入序列可能导致上述哈希表状态(假设键以十进制表示)?( )
- 选项 (A) 中的序列不可能,因为包含键
4 (= 0100)
,但最终哈希表中没有该键。 - 选项 (B) 中的序列不可能,因为键
1 (=0001)
和9 (=1001)
在基于第三最低有效位的 0 侧解决冲突的方式与最终结果不符。 - 选项 (C) 是正确的序列,可得到给定的最终哈希表。
- 选项 (D) 中的序列不可能,因为在第三列中包含键
6 (=0110)
、10 (=1010)
和14 (=1110)
,而这些键在最终哈希表中并未出现。
17
考虑形式为
$$h(k,i) = (h₁(k) + i·h₂(k))\ mod\ m$$
的双重哈希,其中
$$h₁(k) = k\ mod\ m$$
$$h₂(k) = 1 + (k\ mod\ n)$$
且 $n = m - 1$,$m = 701$。当 $k = 123456$ 时,第一次和第二次探测在槽位上的差值是多少?( )
计算步骤:
基础哈希值 h₁(k):
$$ h₁(123456) = 123456 \mod 701 = 80 $$辅助哈希值 h₂(k):
$$ h₂(123456) = 1 + (123456 \mod 700) = 1 + 256 = 257 $$第一次探测(i=1):
$$ h(123456, 1) = (80 + 1×257) \mod 701 = 337 $$第二次探测(i=2):
$$ h(123456, 2) = (80 + 2×257) \mod 701 = 594 $$两次探测的差值:
$$ 594 - 337 = 257 $$
结论: 正确答案为选项 C。
18
链式哈希表(外部哈希)相较于开放寻址方案的优势是( )
- 在开放寻址方案中,当尝试删除一个元素时,如果在查找过程中遇到空桶(即未被占用的位置),可能会错误地认为该元素不存在,从而导致无法正确删除。
- 链式哈希表(外部哈希)通过将所有具有相同哈希值的元素存储在链表中,避免了这一问题,使得删除操作更加直接和可靠。
- 因此,选项 C 是正确答案。
19
给定一个哈希表 T,其包含 25 个槽位并存储了 2000 个元素,则 T 的负载因子 α 为( )
负载因子 = 元素数量 / 表槽位数量 = $\frac{2000}{25} = 80$。因此选项 (A) 是正确答案。
20
考虑一个大小为 7 的哈希表,起始索引为 0,哈希函数为 (7x + 3) mod 4。假设哈希表初始为空,使用闭散列法将序列 1, 3, 8, 10 插入表中后,下列哪一项是表中的内容?(此处“**”表示表中的空位置)
- 键值集合:{1, 3, 8, 10}
- 哈希函数:h(x) = (7*x + 3) mod 4
计算过程:
- h(1) = (7×1 + 3) mod 4 = 10 mod 4 = 2
- h(3) = (7×3 + 3) mod 4 = 24 mod 4 = 0
- h(8) = (7×8 + 3) mod 4 = 59 mod 4 = 3
- h(10)= (7×10 + 3) mod 4 = 73 mod 4 = 1
哈希表构建:
索引 | 内容 |
---|---|
0 | 3 |
1 | 10 |
2 | 1 |
3 | 8 |
4 | |
5 | |
6 |
因此,选项 (A) 正确。
21
将键值 12、18、13、2、3、23、5 和 15 按顺序插入一个初始为空的长度为 10 的哈希表中。该哈希表使用开放寻址法(Open Addressing)进行冲突处理,哈希函数为 h(k) = k mod 10,且采用线性探测(Linear Probing)。最终得到的哈希表是哪一个?( )
开放寻址法(Open Addressing)是一种哈希表冲突解决方法。当发生哈希冲突时,通过在数组中按一定规则探测其他位置(称为探测序列),直到找到目标记录或空闲槽位。常见的探测方式包括:
- 线性探测:每次以固定步长(通常为 1)进行探测。
- 二次探测:探测间隔随线性增长(索引由二次函数描述)。
- 双重哈希:每个记录的探测间隔由另一个哈希函数计算得出。
本题中,所有键值均通过 h(k) = k mod 10 计算初始位置,并在线性探测下依次解决冲突,最终形成的哈希表对应选项 C。
22
一个长度为 10 的哈希表使用开放寻址法,其哈希函数为 h(k) = k mod 10,并采用线性探测。在向空表中插入 6 个值后,表的状态如下所示。以下哪个选项给出了可能的键值插入顺序?
此处使用哈希函数 h(k)=k mod 10 和线性探测。仔细分析所有选项:
索引 | A | B | C | D |
---|---|---|---|---|
0 | ||||
1 | ||||
2 | 42 | 42 | 42 | 42 |
3 | 52 | 23 | 23 | 33 |
4 | 34 | 34 | 34 | 23 |
5 | 23 | 52 | 52 | 34 |
6 | 46 | 33 | 46 | 46 |
7 | 33 | 46 | 33 | 52 |
8 | ||||
9 |
分析所有选项后,(C) 是正确的可能插入顺序:
(A) 不成立,因为序列中
52
出现在23
之前。- 根据线性探测规则,
23
的哈希位置为 3(23 mod 10 = 3),而52
的哈希位置为 2(52 mod 10 = 2)。若先插入52
,则23
在探测时会占据位置 3,导致后续插入冲突逻辑不一致。
- 根据线性探测规则,
(B) 不成立,因为序列中
33
出现在46
之前。33
的哈希位置为 3(33 mod 10 = 3),而46
的哈希位置为 6(46 mod 10 = 6)。若先插入33
,则46
插入时应直接放置于位置 6,但实际表中46
位于位置 6,与插入顺序矛盾。
(C) 成立,因为:
42
(42 mod 10 = 2)→ 直接插入位置 234
(34 mod 10 = 4)→ 直接插入位置 442
已存在,23
(23 mod 10 = 3)→ 插入位置 352
(52 mod 10 = 2)→ 冲突后探测至位置 533
(33 mod 10 = 3)→ 冲突后探测至位置 7- 所有插入操作均符合线性探测规则。
(D) 不成立,因为序列中
33
出现在23
之前。33
插入时会占据位置 3,导致23
插入时需从位置 3 开始探测,最终插入位置 4 或 5,但实际表中23
位于位置 4,与插入顺序不符。
23
假设我们有 $n$ 个关键字、$m$ 个哈希表槽位,以及两个简单且均匀的哈希函数 $h_1$ 和 $h_2$。进一步假设我们的哈希方案对奇数关键字使用 $h_1$,对偶数关键字使用 $h_2$。那么每个槽位中关键字的期望数量是多少?( )
对于均匀哈希函数而言,无论使用多少个哈希函数,每个槽位中关键字的期望数量始终等于 关键字总数 ÷ 槽位总数。
具体分析如下:
- 哈希函数的均匀性保证了每个关键字被分配到任意槽位的概率相等
- 即使采用多个哈希函数(如本题的 $h_1$ 和 $h_2$),只要它们都满足均匀分布特性
- 最终每个槽位的期望负载仍为 $\frac{n}{m}$
因此,选项 B 是正确答案。
24
以下哪项是哈希的组成部分?( )
哈希的组成部分主要有三个:
- 键(Key):键可以是字符串或整数等任何输入到哈希函数中的内容,该技术用于确定数据结构中存储项目的索引或位置。
- 哈希函数(Hash Function):哈希函数接收输入键,并返回一个称为哈希表的数组中元素的索引。该索引称为哈希索引。
- 哈希表(Hash Table):哈希表是一种使用哈希函数将键映射到值的数据结构。它以关联方式在数组中存储数据,每个数据值都有其唯一的索引。
因此,选项 (D) 是正确答案。
25
以下哪项是一种简单的哈希方法,其中数据直接映射到哈希表中的索引( )。
索引映射(也称为平凡哈希)是一种简单的哈希方法,其中数据直接映射到哈希表中的索引。
此方法中使用的哈希函数通常是恒等函数,该函数将输入数据映射到其自身。
在这种情况下,数据的键被用作哈希表中的索引,值存储在该索引处。
因此选项 B 是正确答案。
26
线性探测法的应用包括( ):
线性探测法是一种哈希冲突处理技术,当发生冲突时,算法会在哈希表中寻找下一个可用的存储位置。其应用包括:
缓存
线性探测法可用于缓存系统,将频繁访问的数据存储在内存中。- 当发生缓存未命中时,数据可通过线性探测法加载到缓存中
- 当发生冲突时,使用缓存中的下一个可用位置存储数据
数据库
线性探测法可用于数据库中存储记录及其关联键值。- 当发生冲突时,通过线性探测法查找下一个可用位置存储记录
编译器设计
线性探测法可用于实现:- 符号表管理
- 错误恢复机制
- 语法分析过程
因此选项 (D) 是正确答案。
27
处理冲突的方法有哪些?( )
- 主要的冲突处理方法有两种:
- 独立链接法(Separate Chaining)
- 开放寻址法(Open Addressing)
- 因此 选项 C 是正确答案
28
考虑一个大小为七、起始索引为零的哈希表,其哈希函数为 (3x + 4) mod 7。假设哈希表初始为空,使用闭散列(closed hashing)插入序列 1, 3, 8, 10 后,表中内容是以下哪一项?注意 _
表示表中的空位置。( )
让我们将值 1, 3, 8, 10 插入大小为 7 的哈希表中:
初始时哈希表为空:
索引 | 内容 |
---|---|
0 | - |
1 | - |
2 | - |
3 | - |
4 | - |
5 | - |
6 | - |
插入 1
- 哈希计算:(3×1 + 4) mod 7 = 7 mod 7 = 0
- 直接放入索引 0
索引 | 内容 |
---|---|
0 | 1 |
1 | - |
2 | - |
3 | - |
4 | - |
5 | - |
6 | - |
插入 3
- 哈希计算:(3×3 + 4) mod 7 = 13 mod 7 = 6
- 直接放入索引 6
索引 | 内容 |
---|---|
0 | 1 |
1 | - |
2 | - |
3 | - |
4 | - |
5 | - |
6 | 3 |
插入 8
- 哈希计算:(3×8 + 4) mod 7 = 28 mod 7 = 0
- 索引 0 被占用,使用线性探测找到下一个可用位置 1
索引 | 内容 |
---|---|
0 | 1 |
1 | 8 |
2 | - |
3 | - |
4 | - |
5 | - |
6 | 3 |
插入 10
- 哈希计算:(3×10 + 4) mod 7 = 34 mod 7 = 6
- 索引 6 被占用,使用线性探测找到下一个可用位置 2
索引 | 内容 |
---|---|
0 | 1 |
1 | 8 |
2 | 10 |
3 | - |
4 | - |
5 | - |
6 | 3 |
最终哈希表状态为:1, 8, 10, _, _, _, 3
10 - 排序
1
快速排序的最坏情况下,其递归式是什么?时间复杂度是多少?
当选择的基准元素始终是已排序数组中的端点元素时,快速排序会进入最坏情况。此时快速排序递归调用一个大小为 0 的子问题和另一个大小为 (n-1) 的子问题。因此递归式为 T(n) = T(n-1) + T(0) + O(n),该表达式可简化为 T(n) = T(n-1) + O(n)。
2
假设我们有一个 O(n) 时间的算法可以找到一个未排序数组的中位数。现在考虑一个快速排序的实现:我们首先使用上述算法找到中位数,然后将该中位数作为主元。这种修改后的快速排序的最坏情况时间复杂度是多少?
如果使用中位数作为主元元素,则所有情况的递归关系式变为 T(n) = 2T(n/2) + O(n)。上述递归关系可以通过主定理(Master Theorem)求解。它符合主定理的第 2 种情形。因此,这种修改后的快速排序的最坏情况时间复杂度为 O(n log n)。
3
以下哪种排序算法在典型实现中不是稳定的排序算法?
快速排序在其典型实现中不是稳定的排序算法。
4
在以下排序算法中,哪种在典型实现下对已排序或几乎排序(最多有 1 或 2 个元素错位)的数组性能最佳?
当输入数组已排序或几乎排序时,插入排序的时间复杂度为线性时间。上述所有其他排序算法在典型实现下所需时间均超过线性时间。
5
假设我们正在使用快速排序法对一个包含八个整数的数组进行排序,并且刚刚完成第一次划分操作后数组的状态如下:
2 5 1 7 9 12 11 10
以下哪个陈述是正确的?
7 和 9 都处于其在已排序数组中的正确位置(即最终位置)。此外,7 左侧的所有元素均小于 7,右侧所有元素均大于 7;9 左侧的所有元素均小于 9,右侧所有元素均大于 9。
6
假设我们正在使用堆排序对一个包含八个整数的数组进行排序,并且刚刚完成了一些堆化(最大堆化或最小堆化)操作。当前数组为:16 14 15 10 12 27 28。已对堆的根节点执行了多少次堆化操作?
在堆排序中,我们首先构建一个堆,然后重复以下操作直到堆大小变为 1:a) 交换根节点与最后一个元素 b) 对根节点调用堆化操作 c) 将堆大小减 1。本题中,观察到数组最后两个元素是最大的两个值,说明这是通过最大堆化操作实现的,且该操作已被执行了 2 次。因此选项 B 为正确答案。
7
冒泡排序(优化版)的最佳时间复杂度是什么?
当输入数据已经按预期输出顺序排列时,冒泡排序达到最佳性能。通过引入一个布尔变量来检测内层循环中是否发生过交换即可实现。该布尔变量用于判断在内层循环中是否有至少一次值的交换。请观察以下代码片段:
int main()
{
int arr[] = {10, 20, 30, 40, 50}, i, j, isSwapped;
int n = sizeof(arr) / sizeof(*arr);
isSwapped = 1;
for(i = 0; i < n - 1 && isSwapped; ++i)
{
isSwapped = 0;
for(j = 0; j < n - i - 1; ++j)
if (arr[j] > arr[j + 1])
{
swap(&arr[j], &arr[j + 1]);
isSwapped = 1;
}
}
for(i = 0; i < n; ++i)
printf("%d ", arr[i]);
return 0;
}
请注意,在上述代码中,外层循环仅运行一次。
8
你需要对 1GB 的数据进行排序,但主内存只有 100MB 可用。哪种排序方法最合适?
该数据可以通过使用合并技术的外部排序来处理。具体步骤如下:
- 将数据分成 10 个组,每组大小为 100MB
- 对每个组单独排序后写入磁盘
- 从每个组中加载 10 个元素到主内存
- 将主内存中的最小元素输出到磁盘,并从被选中元素所在组加载下一个元素
- 循环执行第 4 步直到所有元素都被输出
步骤 3-5 被称为合并技术。
9
在一种修改后的归并排序中,输入数组被分割在数组长度(N)的三分之一位置。以下哪项是该修改后归并排序时间复杂度的最紧上界?
时间复杂度由以下递归关系式给出:$T(N) = T(N/3) + T(2N/3) + N$。求解上述递推关系可得:$T(N) = N(log_{3/2}N)$
10
当输入数组的所有元素都相同时,哪种排序算法所需时间最短?考虑典型的排序算法实现。
当输入数组已排序时,插入排序的时间复杂度为 O(n)。
11
以下哪种排序算法的最坏情况时间复杂度最低?
上述排序算法的最坏情况时间复杂度如下:
- 归并排序:O(n*log(n))
- 冒泡排序:O(n²)
- 快速排序:O(n²)
- 选择排序:O(n²)
12
关于归并排序(Merge Sort),以下哪项描述是正确的?
归并排序满足上述所有三个选项的描述。
13
给定一个数组,其中数字的范围从 1 到 n⁶,哪种排序算法可以在线性时间内对这些数字进行排序?
解析
基数排序的时间复杂度是 $O(d \cdot (n + b))$,其中:
- $d$:最大数的位数(以基数 $b$ 表示)
- $n$:元素数量
- $b$:基数,常取 10 或 2 的幂
对于范围在 $1$ 到 $n^6$ 的数字,其最大值为 $n^6$,所以:
以 10 为基数,数字最多有 $\log_{10}(n^6) = 6 \log_{10} n$ 位数
位数 $d$ 与 $\log n$ 成正比,因此整体复杂度是
$$ O(\log n \cdot (n + b)) $$
若基数 $b$ 是常数,复杂度是接近线性的
因此,在输入规模 $n$ 增大时,基数排序可以实现近似线性甚至线性时间排序。
其他选项分析:
计数排序(Counting Sort) 时间复杂度为 $O(n + k)$,其中 $k$ 是数字范围,即最大值为 $n^6$。 因此复杂度为 $O(n + n^6) = O(n^6)$,远远超过线性,不适用。
快速排序(Quick Sort) 时间复杂度 $O(n \log n)$,不是线性时间。
不可能在线性时间内排序 错误。因为基数排序可以在线性时间内完成(在特定情况下)。
最终选择:基数排序(Radix Sort)
14
你有一个包含 n 个元素的数组。假设你通过始终选择数组的中心元素作为基准元素来实现快速排序。那么最坏情况下的性能最优上界是( )。
对于任何输入,都存在一些排列方式会导致最坏情况为 $O(n^2)$。在某些情况下,选择中间元素可以降低遇到 $O(n^2)$ 的可能性,但最坏情况下仍可能达到 $O(n^2)$。无论我们选择第一个还是中间元素作为基准元素,只要基准位置固定,最坏情况仍会是 $O(n^2)$。而选择随机基准元素则能进一步降低遇到最坏情况(即 $O(n^2)$)的可能性。
15
在由 $n$ 个不同整数构成的排列 $a_1 \cdots a_n$ 中,逆序对是指满足 $i < j$ 且 $a_i > a_j$ 的一对元素 $(a_i, a_j)$。如果输入限制为最多包含 $n$ 个逆序对的 $1 \cdots n$ 排列,则插入排序算法的最坏时间复杂度是多少?( )
插入排序的运行时间为Θ(n + f(n)),其中 f(n) 表示数组中初始存在的逆序对数量。
16
以下哪种对典型快速排序的改进能提升其平均性能,并且在实际应用中通常会被采用?
- 随机选择枢轴以减少最坏情况发生的可能性。
- 对小规模数组调用插入排序以减少递归调用次数。
- 快速排序是尾递归的,因此可以进行尾调用优化。
- 使用线性时间中位数查找算法来选取中位数,从而将最坏情况的时间复杂度降低到 $O(nlog_{2}n)$
第四个优化方案在实践中通常不会被采用,虽然它能将最坏情况的时间复杂度降低到 $O(nlog_{2}n)$,但隐藏的常数因子非常高。
17
以下算法在最佳情况下的时间复杂度正确排序是?
在最佳情况下:
- 快速排序:O (nlogn)
- 归并排序:O (nlogn)
- 插入排序:O (n)
- 选择排序:O (n²)
18
关于插入排序(insertion sort)的以下陈述中,哪一项是正确的?
- 在线排序 - 可以在运行时对列表进行排序
- 稳定排序 - 不会改变相同键值元素的相对顺序。
该算法在元素数量较少时耗时较短,但随着元素数量增加,时间复杂度呈二次方增长。
19
对于数组中的元素超过一百万的情况,哪种排序算法最合适?( )
大多数实际的快速排序实现采用随机化版本。该版本的平均时间复杂度为 O(nLogn)。虽然随机化版本也存在最坏情况,但最坏情况不会针对特定模式(如已排序数组)发生,且在实践中随机化快速排序表现良好。
快速排序也是一种缓存友好的排序算法,在处理数组时具有良好的局部性引用特性。
此外,快速排序是尾递归的,因此可以进行尾调用优化。
20
对以下两个输入使用快速排序进行升序排列,且每次都将第一个元素选为主元:
(i) 1, 2, 3, …, n
(ii) n, n-1, n-2, …, 2, 1
设 C1 和 C2 分别为处理输入 (i) 和 (ii) 时的比较次数。则:
当输入序列本身已按升序或降序排列时,快速排序会进入最坏情况。此时两种输入模式(正序和逆序)都会导致完全相同的划分过程,因此比较次数必然相等。选项 (C) 正确。
21
考虑以下排序算法:
I. 快速排序(Quicksort)
II. 堆排序(Heapsort)
III. 归并排序(Mergesort)
在最坏情况下,哪些算法的执行时间最少?
堆排序和归并排序的最坏情况时间复杂度为 $O(n log_{2}n)$,而快速排序的最坏情况时间复杂度为 $O(n^2)$。因此选项 (B) 正确。
22
以下哪种原地排序算法所需的交换次数最少?( )
选择排序在排序数组时需要的交换次数最少。它最多需要进行 O(n) 次比较来对包含 n 个元素的数组进行排序。因此,选择排序是正确答案。
23
以下哪项关于基于比较的排序算法的说法是不正确的?
堆排序不是基于比较的排序算法的说法不正确。堆排序是一种典型的基于比较的排序算法,其核心操作依赖于元素之间的比较(如父节点与子节点的大小关系判断),而计数排序属于非比较类排序算法。
24
以下哪种算法可以在 $O(n)$ 时间内对范围为 0 到 $(n^2 - 1)$ 的 $n$ 个整数进行升序排序?
选择排序需要 $O(n^2)$ 时间。冒泡排序需要 $O(n^2)$ 时间。基数排序需要 $O(n)$ 时间。插入排序需要 $O(n^2)$ 时间。因此,选项 (C) 是正确的。
25
下列哪种算法设计技术用于归并排序?
归并排序采用分治法对元素进行排序。数组会递归地被分割为两半,直到大小变为 1。当数组大小变为 1 时,合并过程开始执行,并逐步将子数组合并回完整数组。
26
如果我们使用基数排序(Radix Sort)对范围在 $(nk/2, nk]$ 内的 $n$ 个整数进行排序,且 $k>0$ 是与 $n$ 无关的常数,那么所需时间是多少?
基数排序的时间复杂度 = $O(w·n)$,其中 $n$ 是键的数量,$w$ 是字长 = $log(n^k)$。因此 $O(w·n) = O(k·log(n)·n) ⇒ O(nk log_{2}n)$。
27
假设我们使用冒泡排序对 n 个不同的元素进行升序排序。冒泡排序的最佳情况何时发生?
在最佳情况下,数组本身已经有序,但为了验证这一点,冒泡排序仍会执行 O(n) 次比较。因此,冒泡排序在最佳情况下的时间复杂度为 O(n)。选项 A 是正确答案。
28
考虑一种情况,其中交换操作非常昂贵。在以下排序算法中,应优先选择哪一种以通常最小化交换操作的次数?
选择排序进行 O(n) 次交换,这是上述所有排序算法中最小的。因此选项 (B) 是正确答案。