分治算法的标准三步骤,顺序正确的是?
考点:分治三步骤(A1)。
解析:分治三步骤 = 分解(Divide)→ 解决(Conquer)→ 合并(Combine):把大问题分解为规模更小的同构子问题,递归解决子问题,最后把子问题的解合并成原问题的解。✅ 正确
排除法:无(判断题)。混淆点:合并步骤不是"可选"的——归并排序的合并是核心,快排的"合并"是隐式的(分区后基准落位、左右已分离,无需显式合并)。
关联 · 快排与归并都是分治(A2):快排分解=分区、合并=无(自动完成);归并分解=对半切、合并=merge 函数。
判断题:快速排序和归并排序都属于分治算法,但快排的"解决"步骤(分区)做在递归之前,归并的"合并"步骤做在递归之后。
考点:快排与归并都是分治(A2)。
解析:快排的"解决"(分区 partition)在递归之前做——先分区把基准落位,再递归排左右;归并的"合并"在递归之后做——先递归排两半,再 merge。这是两者分治结构的本质差异。✅ 正确
排除法:无(判断题)。混淆点:快排没有显式合并步骤,但它的分区本身就是"合并"的替代——保证左段都 右段。
关联 · 分区操作(B2):分区是快排的核心。
分治递归必须设置出口(边界条件),快排和归并的出口分别是?
考点:递归出口(A3)。
解析:分治递归出口 = 子问题规模足够小、直接返回。快排与归并的出口都是区间长度 (即 )——单个元素(或空区间)天然有序、无需再分。✅ 正确
排除法:无(判断题)。混淆点:出口写成 会漏掉单元素区间(多一次无意义递归但不影响结果);漏掉出口则死递归(P5)。
关联 · 递归无出口(P5):没有出口 → 栈溢出。
分治与减治的区别是?
考点:分治与减治的区别(A4)。
解析:分治:分解成多个子问题、全部解决后合并(归并、快排、最大子段和);减治:每轮只保留一个子问题继续求解(二分查找每次只搜一半、快速幂每次指数减半)。✅ 正确
排除法:无(判断题)。混淆点:减治不合并——二分查找"丢弃"另一半而不是合并两半结果。
关联 · 二分查找是减治(G1):查找类问题常用减治,排序类常用分治。
规模 的问题二分分解为两个 的子问题,若合并代价为 ,则总复杂度为?
考点:分治的复杂度直觉(A5)。
解析:每层两个 子问题 + 合并:第 1 层 、第 2 层 、…、第 层 ——每层合计都是 ,共 层,总 。✅ 正确
排除法:无(判断题)。混淆点:这是"每层求和"的分析法——归并排序、快排平均、堆排序都由此得出 。
关联 · 层数与每层代价(D7):归并就是教科书式的"每层 "。
判断题:分治适用于「子问题互相独立、与原问题同构」的场景;若子问题大量重叠(如朴素递归求斐波那契),直接分治会导致指数级重复计算。
考点:分治的适用条件(A6)。
解析:分治适用于子问题独立、同构的场景。若子问题大量重叠(朴素斐波那契: 展开出 两遍、 三遍…),重复计算量爆炸为指数级——需要记忆化/递推而非纯分治。✅ 正确
排除法:无(判断题)。混淆点:重叠子问题不是分治的锅,是"无缓存递归"的锅;DP 正是"分治 + 缓存"。
关联 · 分治与递推(G5):递推自下而上天然复用子问题结果。
快速排序的核心是选择一个基准元素(pivot)。关于基准的选择,下列说法正确的是?
考点:基准元素(B1)。
解析:基准(pivot)可从首/尾/中间/随机取——选择方式不影响正确性,但影响最坏情况触发概率:固定取首元素 + 已序输入 → 必触发 ;随机/三数取中 → 期望 。✅ 正确
排除法:无(判断题)。混淆点:"基准必须是中间元素"——错,任何位置的元素都能当基准。
关联 · 基准优化(C5):随机基准与三数取中是工程标配。
快速排序的分区(partition)操作完成后,数组的状态是?
考点:分区操作(B2)。
解析:分区(partition)目标:把基准放中间,左侧都 基准、右侧都 基准——注意分区后数组并未整体有序,只是"基准对"(左小右大)。✅ 正确
排除法:无(判断题)。混淆点:一次分区 ≠ 排序完成,后续还要递归左右两段。
关联 · 基准落位(B3):分区后基准位置就是最终位置。
一次分区完成后,基准元素的位置有何特点?
考点:基准落位(B3)。
解析:分区后基准左小右大,该位置就是基准在最终有序数组中的位置——递归时把它排除在外(处理 与 )。✅ 正确
排除法:无(判断题)。混淆点:基准不参与后续递归——若把基准也纳入递归(P2 的 不缩小),死递归。
关联 · 递归子区间(B4):落位是"排除基准"的依据。
对区间 做分区,基准最终落在位置 。接下来快排递归处理哪些区间?
考点:递归子区间(B4)。
解析:分区后基准在 ,递归处理 与 ——基准已落位,排除。✅ 正确
排除法:无(判断题)。混淆点:Hoare 分区写法里分界点是 (左段 、右段 ,),基准可能还在其中一段里,但两段已互不越界。
关联 · 分区返回值含义(J6): 相遇错开,两段分离。
快速排序不稳定的原因是?
考点:不稳定的原因(B5)。
解析:快排不稳定:分区交换可能把相等的元素跨过彼此(如基准与远处相同值交换),相等元素的原始相对顺序被打乱。✅ 正确
排除法:无(判断题)。混淆点:稳定性只关乎"相等元素",与大小关系无关;选择/堆排序同样不稳定,归并/插入/冒泡稳定。
关联 · 稳定性对比(F1):稳定性总表要背熟。
快速排序的平均时间复杂度是?
考点:平均复杂度(B6)。
解析:快排平均 ——期望每层分区比较 、期望层数 。✅ 正确
排除法:无(判断题)。混淆点:平均 ≠ 最坏;最坏 是已序+固定基准的退化场景。
关联 · 平均复杂度来源(C6):期望分析见 C 组。
判断题:快速排序是原地排序(in-place)——分区只在原数组上交换元素,不需要 级别的辅助数组。
考点:原地排序(B7)。
解析:快排是原地排序:分区只做元素交换,除递归栈外不需要 辅助数组(对比归并需要)。✅ 正确
排除法:无(判断题)。混淆点:"原地"指额外空间 或 级别;快排递归栈占 ,仍算原地。
关联 · 空间对比(F2):快排空间优势是其工程主流的原因之一。
快速排序最坏情况 的典型触发场景是?
考点:最坏情况的触发(C1)。
解析:已序(或逆序)+ 固定取首元素 → 每轮分区基准落在端点,只排好 1 个元素,剩余 继续 → 共 层、每层 → 。✅ 正确
排除法:无(判断题)。混淆点:全部相等输入对本实现不退化(每轮基准与自身交换后均分?——其实对 Hoare 分区,全相等时每次分区接近均分,复杂度 )。
关联 · 最坏递归深度(C2):退化时深度 。
个元素的数组,快排最坏情况下递归深度约为?
考点:最坏递归深度(C2)。
解析:最坏(每层只排好 1 个)时递归深度 层——这正是栈溢出风险点( 时深度 100 万层必爆栈)。✅ 正确
排除法:无(判断题)。混淆点:最好情况深度 ;随机基准期望深度 。
关联 · 空间复杂度(C4):栈深度即空间。
快速排序的最好情况是?
考点:最好情况(C3)。
解析:最好情况 = 每轮分区接近均分(基准落在中位附近)→ 深度 、每层 → 。✅ 正确
排除法:无(判断题)。混淆点:已序数组不是最好情况(对固定首基准反而是最坏!)。
关联 · 最好情况(C3 与 B6):平均行为接近最好。
快速排序的空间复杂度是?(不计原数组本身)
考点:空间复杂度(C4)。
解析:快排额外空间 = 递归栈:平均/最好 ,最坏 (退化时深度 )。不计原数组本身。✅ 正确
排除法:无(判断题)。混淆点:不是 ——递归栈也算空间;也不是 。
关联 · 空间对比(F2):对比归并 辅助数组。
避免快排最坏情况的常用优化是?
考点:基准优化(C5)。
解析:随机选基准与三数取中(首/中/尾取中位数)都能避免"已序输入必退化"——随机化后已序输入只是普通输入,期望 。✅ 正确
排除法:无(判断题)。混淆点:优化不改变最坏上界(理论上仍可 ),但消灭了"自然输入触发最坏"的确定性。
关联 · 三数取中(O7):三数取中的实现细节。
判断题:快速排序平均复杂度 中的 来自「期望递归层数」,即使某次分区不均衡,期望意义上总的比较次数仍是 级别。
考点:平均复杂度来源(C6)。
解析:平均分析的 来自期望递归层数:随机基准下每层期望均分程度足够好,总比较期望 。✅ 正确
排除法:无(判断题)。混淆点:期望分析 ≠ 均摊分析;快排的平均是"对随机输入的期望"。
关联 · 复杂度求和直觉(C7):每层 × 期望层数。
快排每层分区合计的代价约为 ,若递归层数期望为 ,总复杂度约为?
考点:复杂度求和直觉(C7)。
解析:每层分区合计 ,期望 层 → 总 。✅ 正确
排除法:无(判断题)。混淆点: 是层内合计——每层所有分区加起来只扫一遍数组。
关联 · 分治的复杂度直觉(A5):同一分析框架。
归并排序的第一步是?
考点:二分序列(D1)。
解析:归并第一步 = 从中间一分为二,递归排序左右两半(分解),最后合并。✅ 正确
排除法:无(判断题)。混淆点:归并没有"选基准"——分解是位置对半,与值无关。
关联 · 快排与归并都是分治(A2):分解方式对比。
归并排序递归到区间长度为 时如何处理?
考点:递归到单个元素(D2)。
解析:区间长度 时单个元素天然有序,直接返回(递归出口)——归并的"解决"在出口处是平凡的。✅ 正确
排除法:无(判断题)。混淆点:出口是 ;长度 0 的区间()同样返回。
关联 · 递归出口(A3):分治出口的统一写法。
归并排序的合并步骤:两个有序序列 与 ,合并结果的前三个元素依次是?
考点:合并两个有序序列(D3)。
解析:双指针合并 与 : 取 1; 取 2; 取 3 → 前三个 1, 2, 3。✅ 正确
排除法:无(判断题)。混淆点:合并是"每次取两头较小者",不是交错取。
关联 · 合并过程输出(K1):代码版同题。
归并排序的合并步骤通常需要?
考点:辅助数组(D4)。
解析:合并需要 临时数组暂存合并结果再拷回——这是归并空间 的来源。✅ 正确
排除法:无(判断题)。混淆点:辅助数组是"每层复用同一个",总空间 而非 。
关联 · 空间对比(F2):快排原地 vs 归并 。
归并排序稳定的原因是?
考点:稳定性(D5)。
解析:合并时左右相等先取左边(a[i] <= a[j] 走左分支)——相等的两个元素,左边的保持在前,相对顺序不变 → 稳定。✅ 正确
排除法:无(判断题)。混淆点:等号写反(P4)立即不稳定;等号归属是稳定性判定的第一细节。
关联 · 稳定性等号反(P4):错误示范。
归并排序的最坏时间复杂度是?
考点:复杂度始终稳定(D6)。
解析:归并对半分解、每层合并 ——与输入分布无关,最好=最坏=平均 。✅ 正确
排除法:无(判断题)。混淆点:这是归并对比快排的核心优势:无最坏退化。
关联 · 最坏情况对比(F3):快排最坏 vs 归并 。
个元素归并排序:递归共 层,每层合并总代价 ,总复杂度是?
考点:层数与每层代价(D7)。
解析:对半分解 层;每层所有合并合计扫 个元素 → 总 。✅ 正确
排除法:无(判断题)。混淆点:每层合计 而非每层 。
关联 · 分治的复杂度直觉(A5):标准"每层求和"。
序列中「逆序对」的定义是?
考点:逆序对定义(E1)。
解析:逆序对 = 且 的数对——前面的数比后面的大。✅ 正确
排除法:无(判断题)。混淆点:升序序列逆序对 = 0;降序 = 。
关联 · 逆序对与冒泡交换次数(E3):冒泡每交换一次消掉一个逆序对。
用归并排序求逆序对:合并左右两半时,若右半边元素 先于左半边剩余元素被取走,则逆序对计数如何增加?
考点:归并计数逆序对(E2)。
解析:合并时若右半边元素 先被取走,说明 小于左半边当前所有剩余元素(它们都在 前面)→ 逆序对 += 左剩余个数()。✅ 正确
排除法:无(判断题)。混淆点:不是加 1——一次取右可能带来多个逆序对。
关联 · 逆序对计数填空(L3/O5):
cnt += m - i + 1是标准句。
判断题:冒泡排序交换相邻元素的次数恰好等于序列的逆序对个数。
考点:逆序对与冒泡交换次数(E3)。
解析:冒泡每次交换相邻元素恰好消掉一个逆序对;排序完成 = 逆序对清零 → 交换次数 = 逆序对总数。✅ 正确
排除法:无(判断题)。混淆点:这是"相邻交换"性质;快排的交换不是相邻交换,无此等式。
关联 · 逆序对定义(E1):升序 0 个 → 冒泡 0 次交换。
归并排序除排序外,典型应用是?
考点:归并的其他应用(E4)。
解析:归并思想三应用:求逆序对(合并时计数)、链表排序(无下标、用指针归并)、外部排序(内存装不下时多路归并磁盘块)。✅ 正确
排除法:无(判断题)。混淆点:最大公约数/素数/最短路与归并无直接关系。
关联 · 归并链表(L4):链表版归并代码。
需要稳定排序时,归并与快排应如何选择?
考点:归并与快排的选择(E5)。
解析:要求稳定 → 归并(稳定);快排不稳定,相等元素相对顺序可能被交换打乱。✅ 正确
排除法:无(判断题)。混淆点:多关键字排序、结构体按主键排且希望副键保序时,稳定性是硬需求。
关联 · 稳定性的应用场景(E6):为什么需要稳定。
稳定性在多关键字排序中的意义是?
考点:稳定性的应用场景(E6)。
解析:稳定排序保证:先按次关键字排、再按主关键字稳定排 → 主关键字相同的记录保持次关键字有序——多级排序的标准套路(如先按成绩排再按学号排,同分者学号有序)。✅ 正确
排除法:无(判断题)。混淆点:不稳定的排序会把第二级顺序打乱,必须先主后次且用稳定排序。
关联 · 稳定性对比(F1):总表。
关于稳定性,快排与归并的正确对比是?
考点:稳定性对比(F1)。
解析:归并稳定、快排不稳定。✅ 正确
排除法:无(判断题)。混淆点:稳不稳定与"快不快"无关——堆排序 也不稳定。
关联 · 常见误区(F6):稳定/原地的常见记反。
关于空间开销,快排与归并的正确对比是?
考点:空间对比(F2)。
解析:快排原地(栈 );归并需要 辅助数组。✅ 正确
排除法:无(判断题)。混淆点:空间是归并的软肋,是快排工程首选的原因之一。
关联 · 空间对比(M5):代码视角验证。
关于最坏时间复杂度,快排与归并的正确对比是?
考点:最坏情况对比(F3)。
解析:快排最坏 (已序+固定基准);归并最坏也是 ——无退化。✅ 正确
排除法:无(判断题)。混淆点:时间复杂度与稳定性是独立维度,别串。
关联 · 复杂度总表(F5):全维度总表。
内存充足、要求排序稳定,且数据规模大——选哪种?
考点:场景选择(F4)。
解析:内存充足 + 要求稳定 + 大数据 → 归并排序( 且稳定; 辅助空间可接受)。✅ 正确
排除法:无(判断题)。混淆点:内存紧张时快排(原地);数据值域小用桶/计数。
关联 · 归并与快排的选择(E5):稳定性是第一筛选条件。
快排与归并的平均/最坏时间、空间、稳定性的正确总表是?
考点:复杂度总表(F5)。
解析:快排:平均 / 最坏 / 空间 / 不稳定;归并:平均=最坏 / 空间 / 稳定。✅ 正确
排除法:无(判断题)。混淆点:四维度(时间平均/时间最坏/空间/稳定性)逐项背熟,S 组单选高频。
关联 · 稳定性总表(J 组 13 章 F5):与 J 组基础排序衔接。
判断题:"快排是稳定排序""归并是原地排序"——这两个说法都正确。
考点:常见误区(F6)。
解析:快排原地但不稳定、归并稳定但不原地(需 辅助数组)——两者在稳定性与空间两个维度上恰好互补。✅ 正确
排除法:无(判断题)。混淆点:"快排稳定""归并原地"是两大高频误记——稳不稳定看相等元素相对顺序,原地与否看额外数组。
关联 · 稳定性对比(F1)/ 空间对比(F2):两个维度的互补性。
二分查找属于分治还是减治?
考点:二分查找是减治(G1)。
解析:二分查找每轮丢弃一半只保留一个子区间继续搜——只解决一个子问题、无合并 → 减治(divide and conquer 的变体)。✅ 正确
排除法:无(判断题)。混淆点:归并要合并两半结果,二分不合并——分治/减治的分水岭是"是否需要合并"。
关联 · 分治与减治的区别(A4):定义对照。
快速幂 的分治/减治思想是?
考点:快速幂(G2)。
解析:: 偶 → ; 奇 → 提出一个 再平方——每次指数减半, 次乘法,对比朴素连乘 。✅ 正确
排除法:无(判断题)。混淆点:二进制视角等价——按 的二进制位自低向高乘入对应权值。
关联 · 快速幂输出(N2)/ 快速幂填空(O6):代码版。
用分治求最大子段和:跨中点的情况如何计算?
考点:分治最大子段和(G3)。
解析:跨中点的最大子段 = 从中点向左的最大后缀和 + 从中点向右的最大前缀和(两段都必经中点);与纯左、纯右三取最大。✅ 正确
排除法:无(判断题)。混淆点:漏跨中点情形 = 经典错误(答案可能偏小);跨中点必须"左后缀+右前缀"而不是"左最大+右最大"。
关联 · 分治最大子段和代码(N3): 分治版。
盘汉诺塔,把 个盘从 A 经 C 移到 B、再把最大盘从 A 移到 C、最后把 个盘从 B 经 A 移到 C——该过程的递归次数满足?
考点:汉诺塔(G4)。
解析:(移 盘两次 + 最大盘一次)→ 展开 。✅ 正确
排除法:无(判断题)。混淆点: 是指数增长——汉诺塔是分治的"反面教材"(子问题两个、规模只减 1)。
关联 · 汉诺塔输出(N4):移动序列的递归打印。
判断题:分治算法自上而下把问题分解再回溯合并,通常用递归实现;递推则自下而上按顺序推算——两者方向相反,但分治的递归树展开后往往也能写成递推(如归并的自底向上迭代版)。
考点:分治与递推(G5)。
解析:分治自上而下(递归分解再回溯合并);递推自下而上(小规模推大规模)——方向相反但可互相转化(归并递归版 ↔ 自底向上迭代版)。✅ 正确
排除法:无(判断题)。混淆点:递归树展开后的计算顺序就是递推顺序。
关联 · 自底向上迭代版(K6/L5):递推化归并。
下列哪个问题也常用分治思想解决?
考点:更多分治实例(G6)。
解析:平面最近点对:按 x 排序二分,跨中线只需检查带内 宽度内的候选点——典型分治(分解+解决+跨区合并)。✅ 正确
排除法:无(判断题)。混淆点:求平方/判奇偶/交换变量是 基本操作,不需要分治。
关联 · 分治的适用条件(A6):几何分治实例。
快排分区代码常写 while (i <= j) 的 i、j 相向扫描。若循环条件误写成 while (i < j),后果是?
考点:分区边界(H1)。
解析:while (i <= j) 保证 i、j 相遇错开后相遇点元素也被处理;写成 i < j 时相遇位置漏判,分区可能错误(P1 实测输出 1 3 2,左段含大于基准的元素)。✅ 正确
排除法:无(判断题)。混淆点:i <= j 与内层 if (i <= j) 配对是相向扫描分区的标准写法。
关联 · 分区边界错(P1):错误示范代码。
判断题:对已经升序排好的数组用「固定取第一个元素为基准」的快排,时间复杂度约为 。
考点:最坏情况误判(H2)。
解析:已升序 + 固定首基准:每轮基准最小、落最左,右段只剩 继续 → 共 层 → 。✅ 正确
排除法:无(判断题)。混淆点:直觉上"已序最好排"恰恰是快排的最坏——这是 S 组最爱的判断题陷阱。
关联 · 最坏情况的触发(C1):同一个坑的概念题。
归并合并时,若只判断"左半边取完"而忘了"右半边也取完"(或漏处理某半边剩余元素),后果是?
考点:合并越界(H3)。
解析:合并必须三循环完整:主循环(两段都剩)+ 左剩余搬移 + 右剩余搬移;漏掉任何一段剩余 → 结果缺元素/脏数据(P3 实测 1 3 0 0)。✅ 正确
排除法:无(判断题)。混淆点:先判断"哪段先耗尽"再搬另一段——两个 while 缺一不可。
关联 · 归并漏剩余段(P3):错误示范代码。
判断题:把 a[i] <= a[j] 中的等号错写成 a[i] < a[j](合并相等元素时先取右边),归并排序的稳定性会被破坏。
考点:稳定性判定综合(H4)。
解析:相等时先取左(<=)是归并稳定的根基;等号写反(<,相等取右)→ 相等元素被"后出现的先放"→ 不稳定(P4 实测 1d 1b 顺序反转)。✅ 正确
排除法:无(判断题)。混淆点:等号归属这一个字符决定稳定性——代码审查必看。
关联 · 稳定性等号反(P4):错误示范代码。
判断题:以下结论全部正确——"快排最坏 但平均 ;归并最坏 但需要 辅助空间;两者都是基于比较的排序,比较排序的最优最坏时间复杂度为 "。
考点:复杂度综合判断(H5)。
解析:三结论全对:① 快排最坏 平均 ;② 归并最坏 需 辅助空间;③ 基于比较的排序最坏下界 (决策树深度 )。✅ 正确
排除法:无(判断题)。混淆点:下界说的是最坏情况——桶/计数等非比较排序可突破到 ,但它们不基于比较。
关联 · 复杂度总表(F5):本章复杂度结论的收官。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {5, 3, 8, 1, 2}; // 基准 x = a[0] = 5 05 int i = 0, j = 4, x = a[0]; 06 while (i <= j) { 07 while (a[i] < x) i++; 08 while (a[j] > x) j--; 09 if (i <= j) { swap(a[i], a[j]); i++; j--; } 10 } 11 for (int k = 0; k < 5; k++) cout << a[k] << " "; 12 return 0; 13}
单选题:程序输出是?
考点:分区过程输出(I1)。
解析::右扫 不大于 5、左扫 不小于 5 → 交换 → ;左扫到 、右扫到 → 交换 → ;i、j 错开结束。输出 2 3 1 8 5。正确答案 A。
实现要点:相向扫描三件套——while (a[i] < x) i++ 找左大、while (a[j] > x) j-- 找右小、if (i <= j) 才交换并各自推进。手算:画出 i、j 每轮停下的位置再交换。
排除法:B 是最终有序结果(一次分区做不到);C 是原数组;D 是错误交换序列。
关联 · 分区操作(B2):概念题代码化。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {5, 3, 8, 1, 2}; // 基准 x = a[0] = 5 05 int i = 0, j = 4, x = a[0]; 06 while (i <= j) { 07 while (a[i] < x) i++; 08 while (a[j] > x) j--; 09 if (i <= j) { swap(a[i], a[j]); i++; j--; } 10 } 11 for (int k = 0; k < 5; k++) if (a[k] == 5) cout << k; 12 return 0; 13}
单选题:程序输出是?(分区后基准 5 的最终下标)
考点:基准落位(I2)。
解析:分区结束 i=3、j=2:左段 全 ,右段 全 ;基准 5 位于右段边界、下标 4,已落位(左侧都不大于它,后续递归不再移动它)。输出 4。正确答案 A。
实现要点:Hoare 分区结束后分界是「i 与 j 错开」——左段 、右段 ;基准不保证居中,但保证左右两段值域分离。手算:写出 i、j 每轮停下的位置。
排除法:B 是初始位置;C 是中间;D 无依据。
关联 · 分区返回值含义(J6):i、j 错开与两段分离。
01#include <bits/stdc++.h> 02using namespace std; 03int a[3] = {3, 1, 2}; 04void qsort(int l, int r) { 05 if (l >= r) return; 06 int x = a[l], i = l, j = r; 07 while (i <= j) { 08 while (a[i] < x) i++; 09 while (a[j] > x) j--; 10 if (i <= j) { swap(a[i], a[j]); i++; j--; } 11 } 12 qsort(l, j); // 第一次递归 13 qsort(i, r); // 第二次递归 14} 15int main() { qsort(0, 2); return 0; }
单选题:第一次递归 qsort(l, j) 被调用时,实参是?
考点:递归调用顺序(I3)。
解析:,:交换 与 → ,i=1、j=1;i 再进到 2,结束。此时 、 → 第一次递归实参 。输出 qsort(0, 1)。正确答案 A。
实现要点:分区后先递归左段 qsort(l, j)、再递归右段 qsort(i, r)——j 是左段右端点、i 是右段左端点,两者错开 。手算:分区结束时记下 i、j 值。
排除法:B 是入口参数;C/D 无依据。
关联 · 快排递归填空(O2):左段实参的填空版。
01#include <bits/stdc++.h> 02using namespace std; 03int a[5] = {5, 1, 4, 2, 3}; 04void qsort(int l, int r) { 05 if (l >= r) return; 06 int x = a[l], i = l, j = r; 07 while (i <= j) { 08 while (a[i] < x) i++; 09 while (a[j] > x) j--; 10 if (i <= j) { swap(a[i], a[j]); i++; j--; } 11 } 12 qsort(l, j); 13 qsort(i, r); 14} 15int main() { 16 qsort(0, 4); 17 for (int k = 0; k < 5; k++) cout << a[k] << " "; 18 return 0; 19}
单选题:程序输出是?
考点:完整快排输出(I4)。
解析:递归执行完整快排后数组整体有序 → 1 2 3 4 5。正确答案 A。
实现要点:完整快排 = 分区 + 递归左段 + 递归右段,递归出口 ;无论中间过程如何,算法保证最终有序。手算技巧:不必模拟每一步,验证出口与两段递归即可。
排除法:B 是降序;C 是首次分区后状态;D 是部分有序。
关联 · 递归子区间(B4):排除基准的两段递归。
01#include <bits/stdc++.h> 02using namespace std; 03int a[3] = {3, 1, 2}; 04int cnt = 0; 05void qsort(int l, int r) { 06 if (l >= r) return; 07 int x = a[l], i = l, j = r; 08 while (i <= j) { 09 while (a[i] < x) i++; 10 while (a[j] > x) j--; 11 if (i <= j) { swap(a[i], a[j]); i++; j--; cnt++; } 12 } 13 qsort(l, j); 14 qsort(i, r); 15} 16int main() { qsort(0, 2); cout << cnt; return 0; }
单选题:程序输出是?(对 {3, 1, 2} 快排全程发生的交换次数)
考点:交换次数(I5)。
解析::第一层交换 与 (1 次);左段 分区交换一次(1 次)→ 共 2 次。正确答案 A。
实现要点:统计 swap 的执行次数——每轮 if (i <= j) 成立才交换;出口处长度为 0/1 的区间不产生交换。手算:递归树每个节点记录一次分区交换数。
排除法:B 漏了左段那次;C 把出口也算上;D 无依据。
关联 · 完整快排输出(I4):同算法加计数器。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {5, 1, 4, 2, 3}; // 基准 x = a[0] = 5 05 int i = 0, j = 4, x = a[0]; 06 while (i <= j) { 07 while (a[i] < x) i++; 08 while (a[j] > x) j--; 09 if (i <= j) { swap(a[i], a[j]); i++; j--; } 10 } 11 cout << i << " " << j; 12 return 0; 13}
单选题:程序输出是?(分区结束后 i 与 j 的值)
考点:分区后状态(I6)。
解析:,:交换 与 → ,i=1、j=3;左扫 1、4、2 都 直到 i=4;右扫 不大于 5 停;? 否 → 结束。i=4、j=3。输出 4 3。正确答案 A。
实现要点:分区结束的 i、j 是错开的( 或更远)——这是外层循环条件 i <= j 的直接后果。手算:跟踪指针每轮位置。
排除法:B 顺序反;C/D 无依据。
关联 · 分区返回值含义(J6):分界点语义。
01#include <bits/stdc++.h> 02using namespace std; 03int n = 5, a[5] = {1, 2, 3, 4, 5}; 04void qsort(int l, int r) { 05 if (l >= r) return; 06 int p = l + rand() % (r - l + 1); // 随机选基准 07 swap(a[l], a[p]); // 换到区间开头 08 int x = a[l], i = l, j = r; 09 while (i <= j) { 10 while (a[i] < x) i++; 11 while (a[j] > x) j--; 12 if (i <= j) { swap(a[i], a[j]); i++; j--; } 13 } 14 qsort(l, j); 15 qsort(i, r); 16} 17int main() { srand(1); qsort(0, 4); for (int k = 0; k < 5; k++) cout << a[k] << " "; return 0; }
判断题:无论随机基准选中哪个元素,该程序最终都会输出 1 2 3 4 5(已升序序列排序后仍有序),且随机基准使已序输入不再必然触发 最坏情况。
考点:随机基准(J1)。
解析:随机基准只是改变"选谁当基准",不改变排序正确性——任何基准最终都得到有序数组 1 2 3 4 5;且已序输入不再必然触发 (期望 )。✅ 正确
实现要点:随机基准套路 = rand() % (r - l + 1) 取区间内随机下标 → 换到 a[l] → 复用固定首基准的分区代码。手算:srand(1) 下结果与平台相关,但正确性恒成立。
排除法:无(判断题)。混淆点:随机化消灭"确定的最坏",最坏上界仍是 。
关联 · 基准优化(C5):随机化与三数取中。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {9, 5, 1, 7, 3}; 05 // 三数取中:取 a[0]、a[2](中间)、a[4] 的中位数作基准 06 int x = a[0], y = a[2], z = a[4]; 07 int mid = x + y + z - max({x, y, z}) - min({x, y, z}); 08 cout << mid; 09 return 0; 10}
单选题:程序输出是?(选出的基准值)
考点:三数取中(J2)。
解析:首 、中 、尾 三个数的中位数:。输出 3。正确答案 A。
实现要点:三数取中 = 首/中/尾取中位数作基准——sum - max - min 是三个数的中位数公式。手算:排序 取中间 3。
排除法:B 是最大;C 是最小;D 是 。
关联 · 三数取中填空(O7):排序法的三数取中。
01#include <bits/stdc++.h> 02using namespace std; 03int a[3] = {3, 1, 2}; 04int main() { 05 stack<pair<int, int>> st; // 用栈模拟递归 06 st.push({0, 2}); 07 while (!st.empty()) { 08 int l = st.top().first, r = st.top().second; st.pop(); 09 if (l >= r) continue; 10 int x = a[l], i = l, j = r; 11 while (i <= j) { 12 while (a[i] < x) i++; 13 while (a[j] > x) j--; 14 if (i <= j) { swap(a[i], a[j]); i++; j--; } 15 } 16 st.push({i, r}); // 后压右边 → 先处理左边(模拟先左后右递归) 17 st.push({l, j}); 18 } 19 for (int k = 0; k < 3; k++) cout << a[k] << " "; 20 return 0; 21}
单选题:程序输出是?
考点:迭代版快排(J3)。
解析:栈模拟递归:先压右段再压左段 → 弹栈先处理左段(与递归先左后右一致)→ 最终 1 2 3。正确答案 A。
实现要点:递归转迭代 = 用栈存 (l, r);后压先弹——想先处理左段就先压右段。手算:画出栈内容变化。
排除法:B 是降序;C/D 无依据。
关联 · 分治与递推(G5):递归的迭代化。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[4] = {4, 1, 3, 2}; 05 int x = a[0]; // 基准 4,挖坑在下标 0 06 int i = 0, j = 3; 07 while (i < j) { 08 while (i < j && a[j] >= x) j--; 09 if (i < j) { a[i] = a[j]; i++; } // 右找小填左坑 10 while (i < j && a[i] <= x) i++; 11 if (i < j) { a[j] = a[i]; j--; } // 左找大填右坑 12 } 13 a[i] = x; // 基准回填 14 for (int k = 0; k < 4; k++) cout << a[k] << " "; 15 return 0; 16}
单选题:程序输出是?
考点:挖坑法分区(J4)。
解析: 挖坑 0;右找小 → 、坑变 3;左找大:1、3 都不大于 4,i 走到 3 与 j 相遇 → 循环退出 → 基准回填 → 。输出 2 1 3 4。正确答案 A。
实现要点:挖坑法 = 基准先取出留坑 → 右找小填左坑、左找大填右坑交替 → 相遇处基准回填。手算:记录"坑"的位置流转。
排除法:B 是全排序结果;C/D 无依据。
关联 · 分区操作(B2):同一目标的不同写法。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {4, 2, 1, 3, 5}; // 基准 = 最后一个元素 5 05 int p = a[4], i = 0; 06 for (int j = 0; j < 4; j++) { 07 if (a[j] < p) { swap(a[i], a[j]); i++; } // 小元素交换到前缀 08 } 09 swap(a[i], a[4]); // 基准归位到 i 10 cout << i; 11 return 0; 12}
单选题:程序输出是?(基准 5 落位后的下标)
考点:单向扫描分区(J5)。
解析:基准 5: 扫描前 4 个元素,凡 就与 交换、i 前进——4、2、1、3 全部 → i 前进到 4;最后基准与 交换(原地)→ 输出 i=4。正确答案 A。
实现要点:Lomuto 单向分区 = i 标记「小元素前缀末尾」,j 扫一遍把小数换到前缀,最后基准归位 。手算:i 只在遇到小于基准时前进。
排除法:B/C/D 无依据。
关联 · 挖坑法分区(J4):分区写法全家桶。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {2, 1, 3, 0}; 04// Hoare 式分区:返回的分界点 j 满足什么? 05int main() { 06 int x = a[0], i = 0, j = 3; 07 while (i <= j) { 08 while (a[i] < x) i++; 09 while (a[j] > x) j--; 10 if (i <= j) { swap(a[i], a[j]); i++; j--; } 11 } 12 cout << j; 13 return 0; 14}
单选题:程序输出是?且分区结束后 与 两段的关系是?
考点:分区返回值含义(J6)。
解析:,:交换 与 → ,i=1、j=2;左扫 → i=2;右扫 → j=1;结束。j=1。左段 全 、右段 全 。输出 1,两段已分离。正确答案 A。
实现要点:Hoare 分区返回 (或 )作为分界—— 与 值域分离是后续 qsort(l, j); qsort(i, r) 的依据。手算:结束时记录 i、j 并检查两段。
排除法:B/C/D 无依据。
关联 · 分区后状态(I6):分界语义。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[6] = {1, 3, 5, 2, 4, 6}; // 左半 [0,2] 与右半 [3,5] 各自有序 05 int tmp[6]; 06 int i = 0, j = 3, k = 0; 07 while (i <= 2 && j <= 5) { 08 if (a[i] <= a[j]) tmp[k++] = a[i++]; 09 else tmp[k++] = a[j++]; 10 } 11 while (i <= 2) tmp[k++] = a[i++]; 12 while (j <= 5) tmp[k++] = a[j++]; 13 for (int t = 0; t < 3; t++) cout << tmp[t] << " "; 14 return 0; 15}
单选题:程序输出是?(合并结果的前 3 个元素)
考点:合并过程输出(K1)。
解析:双指针合并: 取 1、 取 2、 取 3 → 前三个 1 2 3。正确答案 A。
实现要点:合并 = 两个指针各指一段开头,每次取较小者、对应指针前进;一段取尽后整段搬移剩余。手算:写两行有序序列,指针一步步前进。
排除法:B 是左段全部;C 是右段;D 是倒序。
关联 · 合并两个有序序列(D3):概念题代码化。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {3, 1, 4, 2}; 04int tmp[4]; 05void merge(int l, int m, int r) { 06 int i = l, j = m + 1, k = l; 07 while (i <= m && j <= r) { 08 if (a[i] <= a[j]) tmp[k++] = a[i++]; 09 else tmp[k++] = a[j++]; 10 } 11 while (i <= m) tmp[k++] = a[i++]; 12 while (j <= r) tmp[k++] = a[j++]; 13 for (int t = l; t <= r; t++) a[t] = tmp[t]; 14} 15void msort(int l, int r) { 16 if (l >= r) return; 17 int m = (l + r) / 2; 18 msort(l, m); 19 msort(m + 1, r); 20 merge(l, m, r); 21} 22int main() { 23 msort(0, 3); 24 for (int k = 0; k < 4; k++) cout << a[k] << " "; 25 return 0; 26}
单选题:程序输出是?
考点:完整归并输出(K2)。
解析::两半各自有序后合并 → 最终 1 2 3 4。正确答案 A。
实现要点:完整归并 = 递归两半 + 合并;正确性不依赖输入顺序。手算技巧:验证出口、中点、合并三处即可,不必全模拟。
排除法:B 是降序;C 是原数组;D 是部分有序。
关联 · 归并递归填空(O4):填空版。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {3, 1, 4, 2}; 04int tmp[4]; 05void merge(int l, int m, int r) { 06 cout << l << m << r << " "; // 打印每次合并的区间端点 07 int i = l, j = m + 1, k = l; 08 while (i <= m && j <= r) { 09 if (a[i] <= a[j]) tmp[k++] = a[i++]; 10 else tmp[k++] = a[j++]; 11 } 12 while (i <= m) tmp[k++] = a[i++]; 13 while (j <= r) tmp[k++] = a[j++]; 14 for (int t = l; t <= r; t++) a[t] = tmp[t]; 15} 16void msort(int l, int r) { 17 if (l >= r) return; 18 int m = (l + r) / 2; 19 msort(l, m); 20 msort(m + 1, r); 21 merge(l, m, r); 22} 23int main() { msort(0, 3); return 0; }
单选题:程序输出是?(merge 被调用的区间端点序列,每次打印 l、m、r 三个数)
考点:递归顺序(K3)。
解析:msort(0,3) → msort(0,1) → 合并 0 0 1;msort(2,3) → 合并 2 2 3;最后合并 0 1 3。输出 0 0 1 2 2 3 0 1 3。正确答案 A。
实现要点:归并是后序遍历——先合并最底层相邻单元素,最后合并整个区间。手算:画递归树,merge 打印发生在子树都返回之后。
排除法:B/C/D 顺序错。
关联 · 递归顺序(I3):快排先分区后递归 vs 归并先递归后合并。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {1, 2, 3, 4}; 04int tmp[4]; 05void merge(int l, int m, int r) { 06 int i = l, j = m + 1, k = l; 07 while (i <= m && j <= r) { 08 if (a[i] <= a[j]) tmp[k++] = a[i++]; 09 else tmp[k++] = a[j++]; 10 } 11 while (i <= m) tmp[k++] = a[i++]; 12 while (j <= r) tmp[k++] = a[j++]; 13 for (int t = l; t <= r; t++) a[t] = tmp[t]; 14} 15int main() { 16 merge(0, 1, 3); // 直接合并 [0,1] 与 [2,3] 两段(各自有序) 17 for (int k = 0; k < 4; k++) cout << a[k] << " "; 18 return 0; 19}
单选题:程序输出是?
考点:辅助数组使用(K4)。
解析: 与 合并:依次取 1、2、3、4 → 拷回后 1 2 3 4。正确答案 A。
实现要点:辅助数组 tmp 的下标 k 独立于 i、j;合并完必须把 tmp 拷回 a(区间 )。手算:k 从 l 起步与 i、j 同步前进。
排除法:B/C/D 无依据。
关联 · 辅助数组(D4): 空间来源。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {2, 1, 4, 3}; 04int tmp[4]; 05int cnt = 0; 06void merge(int l, int m, int r) { 07 int i = l, j = m + 1, k = l; 08 while (i <= m && j <= r) { 09 cnt++; // 每次比较计数 10 if (a[i] <= a[j]) tmp[k++] = a[i++]; 11 else tmp[k++] = a[j++]; 12 } 13 while (i <= m) tmp[k++] = a[i++]; 14 while (j <= r) tmp[k++] = a[j++]; 15 for (int t = l; t <= r; t++) a[t] = tmp[t]; 16} 17void msort(int l, int r) { 18 if (l >= r) return; 19 int m = (l + r) / 2; 20 msort(l, m); 21 msort(m + 1, r); 22 merge(l, m, r); 23} 24int main() { msort(0, 3); cout << cnt; return 0; }
单选题:程序输出是?(归并全程的比较次数)
考点:比较次数(K5)。
解析::合并 比较 1 次(2 vs 1)、合并 比较 1 次(4 vs 3)、合并 比较 2 次(1 vs 3、2 vs 3,之后左段耗尽)→ 共 4 次。正确答案 A。
实现要点:比较次数 = 主循环迭代次数(两段都有剩余时才比较);剩余搬移不算比较。手算:每层合并记录主循环次数。
排除法:B 漏了顶层;C/D 无依据。
关联 · 交换次数(I5):快排数交换 vs 归并数比较。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[4] = {3, 1, 4, 2}; 05 // 自底向上归并:第一轮步长 len = 1,相邻单个元素两两合并 06 int len = 1; 07 for (int s = 0; s + len < 4; s += 2 * len) { 08 if (a[s] > a[s + len]) swap(a[s], a[s + len]); 09 } 10 for (int k = 0; k < 4; k++) cout << a[k] << " "; 11 return 0; 12}
单选题:程序输出是?(第一轮合并后的数组)
考点:自底向上迭代版(K6)。
解析:步长 1 第一轮:合并 与 → ;合并 与 → → 。输出 1 3 2 4。正确答案 A。
实现要点:自底向上 = 步长 len 从 1 翻倍,每轮把相邻两段(各长 len)合并;一轮内所有段互不重叠。手算:按 len 分组看合并对。
排除法:B 是全部完成;C/D 无依据。
关联 · 自底向上填空(L5):完整迭代版。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {1, 3, 2, 4}; 04int tmp[4]; 05void merge(int l, int m, int r) { 06 int i = l, j = m + 1, k = l; 07 while (i <= m && j <= r) { 08 if (______) tmp[k++] = a[i++]; // 先取左边 → 保持稳定性 09 else tmp[k++] = a[j++]; 10 } 11 while (i <= m) tmp[k++] = a[i++]; 12 while (j <= r) tmp[k++] = a[j++]; 13 for (int t = l; t <= r; t++) a[t] = tmp[t]; 14} 15int main() { merge(0, 1, 3); for (int k = 0; k < 4; k++) cout << a[k] << " "; return 0; }
单选题:横线处应填入?(使输出为 1 2 3 4 且保持稳定性)
考点:合并填空(L1)。
解析:保持稳定需相等时取左:a[i] <= a[j]。输出 1 2 3 4。正确答案 A。
实现要点:等号归属决定稳定性——<= 取左(稳定),< 取右(不稳定)。手算:构造相等元素验证。
排除法:B 把升序变降序;C 不稳定;D 无依据。
关联 · 稳定性(D5):等号是稳定性的关键字符。
01#include <bits/stdc++.h> 02using namespace std; 03int a[3] = {3, 1, 2}; 04int tmp[3]; 05long long cnt = 0; 06void merge(int l, int m, int r) { 07 int i = l, j = m + 1, k = l; 08 while (i <= m && j <= r) { 09 if (a[i] <= a[j]) tmp[k++] = a[i++]; 10 else { cnt += (m - i + 1); tmp[k++] = a[j++]; } // 右元素先取 → 左剩余全构成逆序对 11 } 12 while (i <= m) tmp[k++] = a[i++]; 13 while (j <= r) tmp[k++] = a[j++]; 14 for (int t = l; t <= r; t++) a[t] = tmp[t]; 15} 16void msort(int l, int r) { 17 if (l >= r) return; 18 int m = (l + r) / 2; 19 msort(l, m); 20 msort(m + 1, r); 21 merge(l, m, r); 22} 23int main() { msort(0, 2); cout << cnt; return 0; }
单选题:程序输出是?(序列 {3, 1, 2} 的逆序对个数)
考点:逆序对计数输出(L2)。
解析::合并 时右元素 1 先取 → cnt += 1;合并 时右元素 2 先取(左剩 )→ cnt += 1 → 共 2。正确答案 A。
实现要点:右元素先取 → 左段当前剩余全部大于它 → cnt += m - i + 1。手算:按"右先取"事件累加。
排除法:B 漏顶层;C 把交换当计数;D 无依据。
关联 · 归并计数逆序对(E2):原理代码化。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {4, 3, 2, 1}; 04int tmp[4]; 05long long cnt = 0; 06void merge(int l, int m, int r) { 07 int i = l, j = m + 1, k = l; 08 while (i <= m && j <= r) { 09 if (a[i] <= a[j]) tmp[k++] = a[i++]; 10 else { ______; tmp[k++] = a[j++]; } // 累计左半边剩余元素个数 11 } 12 while (i <= m) tmp[k++] = a[i++]; 13 while (j <= r) tmp[k++] = a[j++]; 14 for (int t = l; t <= r; t++) a[t] = tmp[t]; 15} 16void msort(int l, int r) { 17 if (l >= r) return; 18 int m = (l + r) / 2; 19 msort(l, m); 20 msort(m + 1, r); 21 merge(l, m, r); 22} 23int main() { msort(0, 3); cout << cnt; return 0; }
单选题:横线处应填入?(使输出为 6——完全逆序序列的逆序对个数)
考点:逆序对计数填空(L3)。
解析:右先取时加左剩余个数:cnt += m - i + 1; 完全逆序 → 。正确答案 A。
实现要点:m - i + 1 是左段剩余长度( 时);不是右段剩余、也不是固定 1。手算:每层合并检查"右先取"事件。
排除法:B 每次只加 1(漏批量);C 加右剩余(语义反);D 无依据。
关联 · 逆序对计数输出(L2):同一个式子的填空版。
01#include <bits/stdc++.h> 02using namespace std; 03struct Node { int v; Node* nxt; }; 04int main() { 05 // 链表 A: 1 -> 3 -> NULL,链表 B: 2 -> 4 -> NULL 06 Node a2 = {3, nullptr}, a1 = {1, &a2}; 07 Node b2 = {4, nullptr}, b1 = {2, &b2}; 08 Node *p = &a1, *q = &b1; 09 Node head = {0, nullptr}, *t = &head; 10 while (p && q) { // 归并两个有序链表 11 if (p->v <= q->v) { t->nxt = p; p = p->nxt; } 12 else { t->nxt = q; q = q->nxt; } 13 t = t->nxt; 14 } 15 t->nxt = p ? p : q; 16 for (Node* c = head.nxt; c; c = c->nxt) cout << c->v << " "; 17 return 0; 18}
单选题:程序输出是?
考点:归并链表(L4)。
解析:链表归并: 接 1; 接 2; 接 3;最后接 4 → 1 2 3 4。正确答案 A。
实现要点:链表归并用指针穿针:比较两链头值、把较小节点接到尾指针后、对应链头前进;结束接剩余链。手算:画两条链表 + 尾指针。
排除法:B 是左链整体接右链;C/D 无依据。
关联 · 归并的其他应用(E4):链表排序场景。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {3, 1, 4, 2}; 04int tmp[4]; 05int main() { 06 // 自底向上归并:步长从 1 开始逐轮翻倍 07 for (int len = ______; len < 4; len *= 2) { 08 for (int s = 0; s < 4; s += 2 * len) { 09 int m = min(s + len - 1, 3), r = min(s + 2 * len - 1, 3); 10 int i = s, j = m + 1, k = s; 11 while (i <= m && j <= r) { 12 if (a[i] <= a[j]) tmp[k++] = a[i++]; 13 else tmp[k++] = a[j++]; 14 } 15 while (i <= m) tmp[k++] = a[i++]; 16 while (j <= r) tmp[k++] = a[j++]; 17 for (int t = s; t <= r; t++) a[t] = tmp[t]; 18 } 19 } 20 for (int k = 0; k < 4; k++) cout << a[k] << " "; 21 return 0; 22}
单选题:横线处应填入?(使输出为 1 2 3 4)
考点:自底向上填空(L5)。
解析:步长从 1 开始翻倍——len = 1 起步才能把相邻单元素合并。输出 1 2 3 4。正确答案 A。
实现要点:自底向上三件套 = for (len = 1; len < n; len *= 2) + 内层按 跳段 + 每段内标准合并(注意 min 截断右端点)。手算:len=1、2、4 三轮各看一次数组。
排除法:B 从 0 起步死循环;C 跳过单元素层;D 无依据。
关联 · 自底向上迭代版(K6):第一轮即 len=1。
01#include <bits/stdc++.h> 02using namespace std; 03struct P { int v; char tag; }; 04P a[4] = {{2, 'a'}, {1, 'b'}, {2, 'c'}, {1, 'd'}}; 05P tmp[4]; 06void merge(int l, int m, int r) { 07 int i = l, j = m + 1, k = l; 08 while (i <= m && j <= r) { 09 if (a[i].v <= a[j].v) tmp[k++] = a[i++]; // 相等先取左 → 稳定 10 else tmp[k++] = a[j++]; 11 } 12 while (i <= m) tmp[k++] = a[i++]; 13 while (j <= r) tmp[k++] = a[j++]; 14 for (int t = l; t <= r; t++) a[t] = tmp[t]; 15} 16void msort(int l, int r) { 17 if (l >= r) return; 18 int m = (l + r) / 2; 19 msort(l, m); 20 msort(m + 1, r); 21 merge(l, m, r); 22} 23int main() { 24 msort(0, 3); 25 for (int k = 0; k < 4; k++) cout << a[k].v << a[k].tag << " "; 26 return 0; 27}
单选题:程序输出是?
考点:稳定性验证输出(L6)。
解析:归并稳定:值 1 的两个元素保持 b 在前 d 在后;值 2 的 a 在前 c 在后 → 1b 1d 2a 2c。正确答案 A。
实现要点:稳定性的实测验证 = 给相等元素打上 tag,排序后检查 tag 顺序。手算:合并时相等取左即可推得。
排除法:B/C/D 都把相等元素的 tag 顺序打乱(不稳定表现)。
关联 · 稳定性等号反(P4):等号写反的对照实验。
01#include <bits/stdc++.h> 02using namespace std; 03int a[5] = {1, 2, 3, 4, 5}; // 已升序 04void qsort(int l, int r) { 05 if (l >= r) return; 06 int x = a[l], i = l, j = r; // 固定取首元素为基准 07 while (i <= j) { 08 while (a[i] < x) i++; 09 while (a[j] > x) j--; 10 if (i <= j) { swap(a[i], a[j]); i++; j--; } 11 } 12 qsort(l, j); 13 qsort(i, r); 14} 15int main() { qsort(0, 4); return 0; }
判断题:对已升序数组 {1,2,3,4,5} 使用「固定取首元素为基准」的快排,其时间复杂度约为 ——这是快排最坏情况的典型触发方式。
考点:复杂度场景(M1)。
解析:已升序 + 固定首基准:每轮基准最小、落最左,右段每次只缩 1 → 共 层、每层 → 。✅ 正确
实现要点:判断快排复杂度看"基准落在哪"——落端点 → 退化;落中位 → 。手算:追踪每轮分区后左右段长度。
排除法:无(判断题)。混淆点:已序输入对快排是最坏,对归并是普通输入。
关联 · 最坏情况的触发(C1):代码版实证。
01#include <bits/stdc++.h> 02using namespace std; 03struct P { int v; char tag; }; 04P a[4] = {{1, 'x'}, {2, 'a'}, {2, 'b'}, {3, 'y'}}; 05int main() { 06 // 用不稳定的分区方式处理:相等元素跨越交换 07 int i = 0, j = 3, x = a[2].v; // 基准取 a[2].v = 2 08 while (i <= j) { 09 while (a[i].v < x) i++; 10 while (a[j].v > x) j--; 11 if (i <= j) { swap(a[i], a[j]); i++; j--; } 12 } 13 for (int k = 0; k < 4; k++) cout << a[k].v << a[k].tag << " "; 14 return 0; 15}
单选题:程序输出是?(观察值相同的 2a、2b 的相对顺序)
考点:稳定性实测(M2)。
解析:基准 :左扫到 (值 2 不小于 2 停)、右扫 值 3 大于 2 → j 退到 2(值 2 停)→ 交换 与 → ——2a 与 2b 顺序反转 → 分区不稳定。正确答案 A。
实现要点:不稳定 = 相等元素在一次交换中跨过彼此;实测时给相等元素打 tag 看顺序。手算:找到相等元素对,看交换是否跨过。
排除法:B 是稳定表现(与结果相反);C/D 无依据。
关联 · 不稳定的原因(B5):原理的实证。
01#include <bits/stdc++.h> 02using namespace std; 03int a[5] = {1, 2, 3, 4, 5}; 04int dep = 0, mx = 0; 05void qsort(int l, int r) { 06 if (l >= r) return; 07 dep++; 08 mx = max(mx, dep); 09 int x = a[l], i = l, j = r; 10 while (i <= j) { 11 while (a[i] < x) i++; 12 while (a[j] > x) j--; 13 if (i <= j) { swap(a[i], a[j]); i++; j--; } 14 } 15 qsort(l, j); 16 qsort(i, r); 17 dep--; 18} 19int main() { qsort(0, 4); cout << mx; return 0; }
单选题:程序输出是?(对已升序数组、固定首元素基准的快排,最大递归深度;dep++ 只对真正进入分区的调用计数,叶子出口不计)
考点:递归深度输出(M3)。
解析:已升序 + 首基准:每层只排好 1 个,进入分区的调用链长 4(区间 ),叶子出口 在 dep++ 前就返回 → mx=4。正确答案 D。
实现要点:深度计数要看清 dep++ 的位置——本题叶子()不计数;若把出口也计进去则是 5。手算:画递归调用链,数"进入分区"的节点。
排除法:A 把叶子也算了;B 是平衡时深度;C 无依据。
关联 · 最坏递归深度(C2):深度 的实证(本题 时含出口节点共 5 层调用)。
01#include <bits/stdc++.h> 02using namespace std; 03// 固定取首元素为基准的快排(省略函数体,行为与前述一致) 04void qsort(int l, int r);
单选题:下列哪个输入对「固定取首元素为基准」的快排而言最接近最坏情况 ?
考点:最坏输入识别(M4)。
解析:A 已升序:首基准最小、每轮只缩 1 → ;B、C 随机排列分区相对均衡;D 全相等:每轮交换后左右均分(本实现中基准与相等元素交换、区间近均分)→ 。正确答案 A。
实现要点:识别最坏 = 问"每轮基准是不是总落在端点"——已序/逆序 + 固定首基准是经典触发器。手算:对每个输入跑一层分区看左右段大小。
排除法:B/C 随机分布;D 全相等并不退化。
关联 · 最坏情况误判(H2):同坑概念题。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int n = 1000000; 05 // 排序算法 A 需要数组: int tmp[n] 06 // 排序算法 B 只需要几个变量(原地交换) 07 cout << "A 空间 O(n),B 空间 O(log n)"; 08 return 0; 09}
判断题:算法 A 是归并排序(辅助数组 ),算法 B 是快速排序(递归栈 )——该描述正确。
考点:空间对比(M5)。
解析:归并辅助数组 、快排递归栈 ——描述正确。✅ 正确
实现要点:空间看"额外分配"——归并 tmp[n]、快排栈深度。手算: 时归并约 4MB 数组、快排栈约 20 层。
排除法:无(判断题)。混淆点:快排最坏栈 ,但平均 。
关联 · 空间对比(F2):代码视角验证。
01#include <bits/stdc++.h> 02using namespace std; 03int a[6] = {5, 2, 4, 1, 3, 0}; 04void qsort(int l, int r) { 05 if (r - l + 1 <= 3) { // 小规模改用插入排序 06 for (int i = l + 1; i <= r; i++) { 07 int t = a[i], j = i - 1; 08 while (j >= l && a[j] > t) { a[j + 1] = a[j]; j--; } 09 a[j + 1] = t; 10 } 11 return; 12 } 13 int x = a[l], i = l, j = r; 14 while (i <= j) { 15 while (a[i] < x) i++; 16 while (a[j] > x) j--; 17 if (i <= j) { swap(a[i], a[j]); i++; j--; } 18 } 19 qsort(l, j); 20 qsort(i, r); 21} 22int main() { qsort(0, 5); for (int k = 0; k < 6; k++) cout << a[k] << " "; return 0; }
单选题:程序输出是?
考点:小规模插排混合优化(M6)。
解析:长度 的区间走插入排序、其余走快排分区递归——正确性不变,最终 0 1 2 3 4 5。正确答案 A。
实现要点:混合优化 = 小规模(如 )改用插入排序(常数小、对近有序快),减少递归开销。手算:出口改为"小规模插排"而非 。
排除法:B 是降序;C/D 无依据。
关联 · 分治的适用条件(A6):规模小到一定程度分治不划算。
01#include <bits/stdc++.h> 02using namespace std; 03int main() { 04 int a[5] = {1, 3, 5, 7, 9}; 05 int l = 0, r = 4, x = 7, ans = -1; 06 while (l <= r) { 07 int m = (l + r) / 2; 08 if (a[m] == x) { ans = m; break; } 09 else if (a[m] < x) l = m + 1; 10 else r = m - 1; 11 } 12 cout << ans; 13 return 0; 14}
单选题:程序输出是?
考点:二分查找输出(N1)。
解析: 找 7:中点 5 小于 7 → 右半;中点 7 命中 → 下标 3。正确答案 A。
实现要点:二分 = 中点比较、对半收缩;命中即返回下标。手算:写出每轮的 l、r、mid。
排除法:B 是 5 的位置;C/D 无依据。
关联 · 二分查找是减治(G1):减治实例。
01#include <bits/stdc++.h> 02using namespace std; 03long long qpow(long long a, long long n) { 04 long long r = 1; 05 while (n) { 06 if (n & 1) r = r * a; // 二进制位为 1 时乘入 07 a = a * a; // 底数平方 08 n >>= 1; // 指数右移 09 } 10 return r; 11} 12int main() { cout << qpow(2, 10); return 0; }
单选题:程序输出是?
考点:快速幂输出(N2)。
解析::指数二进制 1010——。正确答案 A。
实现要点:二进制快速幂 = 指数位为 1 时乘入当前底数、底数每轮平方。手算:按二进制位拆分指数。
排除法:B 是近似值;C 是 ;D 是 。
关联 · 快速幂(G2):概念题代码化。
01#include <bits/stdc++.h> 02using namespace std; 03int a[9] = {-2, 1, -3, 4, -1, 2, 1, -5, 4}; 04// 分治求最大子段和:左半最大 / 右半最大 / 跨中点最大 三者取大 05int solve(int l, int r) { 06 if (l == r) return a[l]; 07 int m = (l + r) / 2; 08 int lm = solve(l, m), rm = solve(m + 1, r); // 左右子问题 09 int lsum = -1e9, s = 0; 10 for (int i = m; i >= l; i--) { s += a[i]; lsum = max(lsum, s); } // 跨中点左后缀 11 int rsum = -1e9; s = 0; 12 for (int i = m + 1; i <= r; i++) { s += a[i]; rsum = max(rsum, s); } // 跨中点右前缀 13 return max({lm, rm, lsum + rsum}); 14} 15int main() { cout << solve(0, 8); return 0; }
单选题:程序输出是?
考点:分治最大子段和(N3)。
解析:最优子段 (和 6)落在右半并跨中点;分治三路取大得 6。正确答案 A。
实现要点:跨中点 = 左半后缀最大 + 右半前缀最大(都必须"贴中点");与纯左、纯右三取大。手算:从中点向两侧累加找最大前后缀。
排除法:B 漏了 1;C/D 无依据。
关联 · 分治最大子段和(G3):概念题代码化。
01#include <bits/stdc++.h> 02using namespace std; 03void hanoi(int n, char a, char b, char c) { // n 盘:a 经 b 移到 c 04 if (n == 0) return; 05 hanoi(n - 1, a, c, b); 06 cout << a << "->" << c << " "; 07 hanoi(n - 1, b, a, c); 08} 09int main() { hanoi(2, 'A', 'B', 'C'); return 0; }
单选题:程序输出是?(2 个盘的最优移动序列)
考点:汉诺塔输出(N4)。
解析:2 盘:小盘 A→B、大盘 A→C、小盘 B→C → A->B A->C B->C。正确答案 A。
实现要点:递归框架 = hanoi(n-1, a, c, b)(移走上面 n-1 盘到辅助柱)+ 移最大盘 + hanoi(n-1, b, a, c)。手算:2 盘三步、3 盘七步。
排除法:B 是第一轮 3 盘的片段;C/D 无依据。
关联 · 汉诺塔(G4):递推 。
01#include <bits/stdc++.h> 02using namespace std; 03long long qpow(long long a, long long n, long long p) { 04 long long r = 1; 05 a %= p; 06 while (n) { 07 if (n & 1) r = r * a % p; 08 a = a * a % p; 09 n >>= 1; 10 } 11 return r; 12} 13int main() { cout << qpow(2, 10, 1000); return 0; }
单选题:程序输出是?
考点:快速幂取模(N5)。
解析:。正确答案 A。
实现要点:取模版快速幂 = 每步乘法后都 % p(先各取模再乘再取模),防止中途溢出。手算:拆指数按位乘入取模。
排除法:B 忘了取模;C/D 无依据。
关联 · 模的乘法性质(J 组 18 章 D6):取模安全公式。
01#include <bits/stdc++.h> 02using namespace std; 03int a[5] = {1, 2, 3, 4, 5}; 04int sum(int l, int r) { 05 if (l == r) return a[l]; 06 int m = (l + r) / 2; 07 return sum(l, m) + sum(m + 1, r); 08} 09int main() { cout << sum(0, 4); return 0; }
单选题:程序输出是?
考点:分治求和(N6)。
解析:——分治求和 = 左半和 + 右半和。正确答案 A。
实现要点:分治求和是最简分治模板:出口单元素、递归两半、合并做加法。手算:递归树自底向上加。
排除法:B 漏了 5;C 多加;D 无依据。
关联 · 分治三步骤(A1):合并 = 加法的最简实例。
01#include <bits/stdc++.h> 02using namespace std; 03int a[5] = {4, 2, 5, 1, 3}; 04void qsort(int l, int r) { 05 if (l >= r) return; 06 int x = a[l], i = l, j = r; 07 while (______) { // 相向扫描循环条件 08 while (a[i] < x) i++; 09 while (a[j] > x) j--; 10 if (i <= j) { swap(a[i], a[j]); i++; j--; } 11 } 12 qsort(l, j); 13 qsort(i, r); 14} 15int main() { qsort(0, 4); for (int k = 0; k < 5; k++) cout << a[k] << " "; return 0; }
单选题:横线处应填入?(使输出为 1 2 3 4 5)
考点:快排分区填空(O1)。
解析:相向扫描外层条件是 i <= j(保证相遇点也被处理)。输出 1 2 3 4 5。正确答案 A。
实现要点:while (i <= j) 让 i、j 相遇后再错开,两段才彻底分离。手算:检查相遇点元素是否被正确处理。
排除法:B 相遇点漏处理(P1);C 只处理一次;D 反向。
关联 · 分区边界(H1):边界条件的概念题。
01#include <bits/stdc++.h> 02using namespace std; 03int a[5] = {4, 2, 5, 1, 3}; 04void qsort(int l, int r) { 05 if (l >= r) return; 06 int x = a[l], i = l, j = r; 07 while (i <= j) { 08 while (a[i] < x) i++; 09 while (a[j] > x) j--; 10 if (i <= j) { swap(a[i], a[j]); i++; j--; } 11 } 12 ______; // 左段递归 13 qsort(i, r); // 右段递归 14} 15int main() { qsort(0, 4); for (int k = 0; k < 5; k++) cout << a[k] << " "; return 0; }
单选题:横线处应填入?(分区结束后 i > j,两段为 [l, j] 与 [i, r])
考点:快排递归填空(O2)。
解析:分区后左段 :填 qsort(l, j);右段已给 qsort(i, r)。输出 1 2 3 4 5。正确答案 A。
实现要点:分界是 j 与 i(错开)——左段端点用 j、右段用 i,不能用 i-1/j+1 混搭。手算:分区结束记录 i、j。
排除法:B 用 i 做左段右端点(区间重叠);C/D 无依据。
关联 · 递归子区间(B4):两段递归的实参。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {1, 3, 2, 4}; 04int tmp[4]; 05void merge(int l, int m, int r) { 06 int i = l, j = m + 1, k = l; 07 while (i <= m && j <= r) { 08 if (a[i] <= a[j]) tmp[k++] = a[i++]; 09 else tmp[k++] = a[j++]; 10 } 11 while (______) tmp[k++] = a[i++]; // 左半边剩余 12 while (j <= r) tmp[k++] = a[j++]; // 右半边剩余 13 for (int t = l; t <= r; t++) a[t] = tmp[t]; 14} 15int main() { merge(0, 1, 3); for (int k = 0; k < 4; k++) cout << a[k] << " "; return 0; }
单选题:横线处应填入?(使输出为 1 2 3 4)
考点:归并合并填空(O3)。
解析:左半剩余搬移条件是 i <= m(左指针未过中点)。输出 1 2 3 4。正确答案 A。
实现要点:两个剩余搬移循环各用各的边界——左段 、右段 ;缺一个漏数据(P3)。手算:构造"右半先耗尽"的输入验证。
排除法:B 少搬左段末元素;C/D 边界错。
关联 · 合并越界(H3):剩余搬移的纪律。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {3, 1, 4, 2}; 04int tmp[4]; 05void merge(int l, int m, int r) { 06 int i = l, j = m + 1, k = l; 07 while (i <= m && j <= r) { 08 if (a[i] <= a[j]) tmp[k++] = a[i++]; 09 else tmp[k++] = a[j++]; 10 } 11 while (i <= m) tmp[k++] = a[i++]; 12 while (j <= r) tmp[k++] = a[j++]; 13 for (int t = l; t <= r; t++) a[t] = tmp[t]; 14} 15void msort(int l, int r) { 16 if (l >= r) return; 17 int m = (l + r) / 2; 18 ______; // 递归左半 19 msort(m + 1, r); // 递归右半 20 merge(l, m, r); 21} 22int main() { msort(0, 3); for (int k = 0; k < 4; k++) cout << a[k] << " "; return 0; }
单选题:横线处应填入?(使输出为 1 2 3 4)
考点:归并递归填空(O4)。
解析:左半递归 msort(l, m)(右半已给 msort(m + 1, r))。输出 1 2 3 4。正确答案 A。
实现要点:中点 m 归左半——左段 、右段 ,两段不重不漏。手算:验证合并时左右段拼回整个区间。
排除法:B 漏 m;C 重复 m;D 死递归。
关联 · 归并排序原理(D1):二分序列的代码。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {2, 4, 1, 3}; 04int tmp[4]; 05long long cnt = 0; 06void merge(int l, int m, int r) { 07 int i = l, j = m + 1, k = l; 08 while (i <= m && j <= r) { 09 if (a[i] <= a[j]) tmp[k++] = a[i++]; 10 else { ______; tmp[k++] = a[j++]; } // 累计逆序对 11 } 12 while (i <= m) tmp[k++] = a[i++]; 13 while (j <= r) tmp[k++] = a[j++]; 14 for (int t = l; t <= r; t++) a[t] = tmp[t]; 15} 16void msort(int l, int r) { 17 if (l >= r) return; 18 int m = (l + r) / 2; 19 msort(l, m); 20 msort(m + 1, r); 21 merge(l, m, r); 22} 23int main() { msort(0, 3); cout << cnt; return 0; }
单选题:横线处应填入?(使输出为 3——序列 {2, 4, 1, 3} 的逆序对个数)
考点:逆序对填空(O5)。
解析:右先取 → 左剩余全构成逆序对:cnt += m - i + 1; 的逆序对 共 3。正确答案 A。
实现要点:m - i + 1 = 左段剩余长度;与 L3 同式。手算:逐次"右先取"事件累加。
排除法:B 每次加 1;C 加右剩余;D 无依据。
关联 · 逆序对计数填空(L3):同式双题强化。
01#include <bits/stdc++.h> 02using namespace std; 03long long qpow(long long a, long long n) { 04 long long r = 1; 05 while (n) { 06 if (______) r = r * a; // 当前二进制位为 1 时乘入 07 a = a * a; 08 n >>= 1; 09 } 10 return r; 11} 12int main() { cout << qpow(3, 4); return 0; }
单选题:横线处应填入?(使输出为 81)
考点:快速幂填空(O6)。
解析:当前二进制位为 1 时乘入:n & 1;。正确答案 A。
实现要点:n & 1 取最低位——为 1 乘入、随后 n >>= 1 看下一位。手算:指数二进制 100 → 乘 。
排除法:B 恒真(每次都乘);C 只在末轮乘;D 判断错对象。
关联 · 快速幂输出(N2):填空版。
01#include <bits/stdc++.h> 02using namespace std; 03int a[5] = {9, 1, 5, 7, 3}; 04int main() { 05 int l = 0, r = 4; 06 int m = (l + r) / 2; 07 // 三数取中:把 a[l]、a[m]、a[r] 的中位数换到 a[l] 作基准 08 if (a[l] > a[m]) swap(a[l], a[m]); 09 if (______) swap(a[l], a[r]); 10 if (a[m] > a[r]) swap(a[m], a[r]); 11 swap(a[l], a[m]); // 中位数换到开头 12 cout << a[0]; 13 return 0; 14}
单选题:横线处应填入?(使输出为基准值 5——首 9、中 5、尾 3 的中位数)
考点:三数取中填空(O7)。
解析:三数排序三连判:a[l] > a[m] 交换 → a[l] > a[r] 交换(填空)→ a[m] > a[r] 交换 → 中位数在 → 换到 。首 9、中 5、尾 3 → 基准 5。正确答案 A。
实现要点:三次比较排序让 , 即中位数。手算:追踪三个位置的值。
排除法:B 方向反(中位数换不走);C/D 无依据。
关联 · 三数取中(J2):公式版与排序版对照。
01#include <bits/stdc++.h> 02using namespace std; 03int a[3] = {2, 3, 1}; 04int main() { 05 int x = a[0], i = 0, j = 2; 06 while (i < j) { // 错误:应 i <= j 07 while (a[i] < x) i++; 08 while (a[j] > x) j--; 09 if (i <= j) { swap(a[i], a[j]); i++; j--; } 10 } 11 for (int k = 0; k < 3; k++) cout << a[k] << " "; 12 return 0; 13}
单选题:程序输出是?(分区后数组状态)
考点:分区边界错(P1)。
解析:,:交换 与 → ,i=1、j=1;外层 i < j 不成立退出——相遇点 未处理,左段含大于基准的元素,分区错误。输出 1 3 2。正确答案 A。
实现要点:i < j 在 i、j 相遇时提前退出,相遇元素未参与比较交换——必须 i <= j。手算:检查分区后两段是否满足"左小右大"。
排除法:B 是正确结果;C/D 无依据。
关联 · 分区边界(H1):错误示范。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {2, 1, 3, 4}; 04void qsort(int l, int r) { 05 if (l > r) return; // 出口用 l > r 06 int x = a[l], i = l, j = r; 07 while (i <= j) { 08 while (a[i] < x) i++; 09 while (a[j] > x) j--; 10 if (i <= j) { swap(a[i], a[j]); i++; j--; } 11 } 12 qsort(l, r); // 错误:递归区间未缩小 13} 14int main() { qsort(0, 3); cout << "run"; return 0; }
单选题:程序会发生什么?(递归调用 qsort(l, r) 参数与入口完全相同)
考点:基准未排除(P2)。
解析:递归调用 qsort(l, r) 与入口参数相同——子问题规模不缩小 → 无限递归 → 栈溢出。正确答案 A。
实现要点:递归必须保证参数严格缩小:正确写法是 qsort(l, j); qsort(i, r)(基准落位后排除)。手算:检查递归实参是否可能等于入口。
排除法:B 无依据;C 语法合法;D 无依据。
关联 · 递归出口(A3):出口 + 缩小缺一不可。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {5, 6, 1, 3}; 04int tmp[4]; 05void merge(int l, int m, int r) { 06 int i = l, j = m + 1, k = l; 07 while (i <= m && j <= r) { 08 if (a[i] <= a[j]) tmp[k++] = a[i++]; 09 else tmp[k++] = a[j++]; 10 } 11 // 错误:少了两个 while——左/右剩余元素未搬入 12 for (int t = l; t <= r; t++) a[t] = tmp[t]; 13} 14int main() { merge(0, 1, 3); for (int k = 0; k < 4; k++) cout << a[k] << " "; return 0; }
单选题:程序输出是?(tmp 未初始化的位置值不确定,此处按常见编译器默认 0 计)
考点:归并漏剩余段(P3)。
解析: 与 合并:主循环取 1、3 后右段耗尽,左段 5、6 未搬(两个 while 被删)→ tmp 尾部保持 0 → 拷回 1 3 0 0。正确答案 A。
实现要点:主循环后必须两个 while 搬剩余——右段先耗尽是常见场景。手算:构造"一段先空"的输入。
排除法:B 是完整正确结果;C 是原数组;D 无依据。
关联 · 合并越界(H3):漏剩余的错误示范。
01#include <bits/stdc++.h> 02using namespace std; 03struct P { int v; char tag; }; 04P a[4] = {{1, 'b'}, {1, 'd'}, {2, 'a'}, {2, 'c'}}; 05P tmp[4]; 06void merge(int l, int m, int r) { 07 int i = l, j = m + 1, k = l; 08 while (i <= m && j <= r) { 09 if (a[i].v < a[j].v) tmp[k++] = a[i++]; // 错误:相等时先取右边 10 else tmp[k++] = a[j++]; 11 } 12 while (i <= m) tmp[k++] = a[i++]; 13 while (j <= r) tmp[k++] = a[j++]; 14 for (int t = l; t <= r; t++) a[t] = tmp[t]; 15} 16void msort(int l, int r) { 17 if (l >= r) return; 18 int m = (l + r) / 2; 19 msort(l, m); 20 msort(m + 1, r); 21 merge(l, m, r); 22} 23int main() { 24 msort(0, 3); 25 for (int k = 0; k < 4; k++) cout << a[k].v << a[k].tag << " "; 26 return 0; 27}
单选题:程序输出是?(观察值相同的元素相对顺序是否被破坏)
考点:稳定性等号反(P4)。
解析:a[i].v < a[j].v 取左,否则取右——相等时取右 → 相等元素被"后出现的先放" → 1d 1b 2c 2a,不稳定。正确答案 A。
实现要点:稳定性取决于等号归属:<= 取左稳定、< 取右不稳定。手算:相等元素对在合并时谁先入 tmp。
排除法:B 是稳定结果;C/D 无依据。
关联 · 稳定性等号反(H4):错误示范。
01#include <bits/stdc++.h> 02using namespace std; 03int a[3] = {3, 1, 2}; 04void qsort(int l, int r) { 05 // 错误:没有 if (l >= r) return; 出口 06 int x = a[l], i = l, j = r; 07 while (i <= j) { 08 while (a[i] < x) i++; 09 while (a[j] > x) j--; 10 if (i <= j) { swap(a[i], a[j]); i++; j--; } 11 } 12 qsort(l, j); 13 qsort(i, r); 14} 15int main() { qsort(0, 2); return 0; }
判断题:缺少 if (l >= r) return 出口,当递归到区间长度 时仍会继续递归,最终导致栈溢出——该说法正确。
考点:递归无出口(P5)。
解析:没有 出口:区间长度 时仍继续递归( 的区间被反复调用)→ 递归永不终止 → 栈溢出。✅ 正确
实现要点:分治递归 = 出口 + 缩小,两者缺一不可(对照 P2)。手算:追踪到单元素区间后会发生什么。
排除法:无(判断题)。混淆点:有些实现出口写 、有些写 ,但必须有。
关联 · 递归出口(A3):出口是分治的组成部分。
01#include <bits/stdc++.h> 02using namespace std; 03int a[4] = {1, 2, 3, 4}; // 已升序 04int calls = 0; 05void qsort(int l, int r) { 06 calls++; // 统计调用次数(含出口判断) 07 if (l >= r) return; 08 int x = a[l], i = l, j = r; // 固定取首元素为基准 09 while (i <= j) { 10 while (a[i] < x) i++; 11 while (a[j] > x) j--; 12 if (i <= j) { swap(a[i], a[j]); i++; j--; } 13 } 14 qsort(l, j); 15 qsort(i, r); 16} 17int main() { qsort(0, 3); cout << calls; return 0; }
单选题:程序输出是?(总调用次数 = 内部节点数 + 叶子出口判断数)
考点:最坏输入识别输出(P6)。
解析: 已升序:调用链 qsort(0,3) → qsort(0,-1)(出口)+ qsort(1,3) → qsort(1,0)(出口)+ qsort(2,3) → qsort(2,1)(出口)+ qsort(3,3)(出口)——共 7 次调用(4 次进入分区 + 3 次出口)。正确答案 A。
实现要点:调用次数 = 递归树节点数;已序输入退化成链 → 内部节点 n 个、出口 n-1 个(本题 4+3)。手算:画递归调用树数节点。
排除法:B 只数内部节点;C/D 无依据。
关联 · 最坏递归深度(C2):退化成链的实证。
判断题:以下结论全部正确——"快排分区后基准落位,递归只处理 [l, j] 与 [i, r] 两段(基准不参与);归并先递归两半再合并,需要 辅助数组;逆序对计数在合并右元素先取时加左剩余个数;快速幂每次把指数折半,复杂度 "。
考点:综合判断(P7)。
解析:四结论全对:① 分区后基准落位、递归只处理 与 (基准不参与);② 归并先递归后合并、需 辅助;③ 逆序对计数右先取时加左剩余个数;④ 快速幂指数折半 。✅ 正确
实现要点:本章四个核心代码模式(分区递归、归并合并、逆序对计数、快速幂)的收官自查。手算:逐条对照本章代码。
排除法:无(判断题)。混淆点:① 的分界是 j/i 不是 p/p+1——两者等价但写法不同。
关联 · 本章全部核心结论:收官综合判断题。