Skip to content

性能分析:为何突破 O(n²)

基于通用算法套路 · 核于 2026-07

速查

  • 核心结论:希尔排序突破了 O(n²) 界线——平均约 O(n^1.3)(依赖 gap 序列),最好约 O(nlog²n) ~ O(nlogn),最坏依赖序列(Shell 原版 O(n²),Knuth O(n^1.5),Sedgewick O(n^(4/3)))。
  • 突破的本质:跨距离交换。插入排序只能相邻交换(一步挪一格),把末尾最小元素挪到首位要 n-1 次;希尔排序大 gap 阶段一次跨越多步,让元素快速接近终位,大幅减少总移动次数。
  • 平均 O(n^1.3):这是经验值和特定 gap 序列(如 Sedgewick)的已证上界,没有严格的数学下界证明——希尔排序的精确复杂度至今仍是开放问题。
  • 最好情况:输入已有序时,每一轮插入排序的 while 都立即 break(a[j-gap] > tmp 不成立),总比较约 n × (gap 轮数) ≈ nlogn,即 最好 O(nlog²n) ~ O(nlogn)
  • 不稳定:分组后相等元素可能被分到不同子序列、跨段交换,相对次序被打乱——这是希尔排序的固有性质,无法靠代码写法修复(不像归并排序)。
  • 空间 O(1):原地排序,只需几个临时变量,无递归(迭代实现),无辅助数组。
  • 为何中等规模实用:n 为几百到几千时,希尔排序常数小、无递归开销、原地,实测常常快于 O(nlogn) 的堆排(堆排常数大、缓存不友好),接近快排——是库排序对小数组兜底的常见选择。
  • 大规模不如快排/归并:最好 gap 序列也只能到 O(n^1.3),数据量很大时渐近输给 O(nlogn) 的快排/归并/堆排。
  • 与插入排序:希尔严格优于插入(插入是 gap={1} 的特例),尤其对逆序/乱序数据。
  • 进阶顺序参考(复杂度表、代码模板、易错点)。

一、为何能突破 O(n²):跨距离交换

插入排序之所以对乱序数据是 O(n²),根源是每次只能消除一个相邻逆序对——元素每次只挪动 1 格。希尔排序通过 gap 分组,让相距 gap 的元素直接比较交换,一次操作就能消除一个跨 gap 的逆序对,等效于「一步挪 gap 格」。

对比:把末尾的最小元素挪到首位
插入排序(gap=1):要和前面 n-1 个元素各比较一次,挪 n-1 格 → O(n)
希尔排序(gap=n/2):
  第一轮 gap=n/2:最小元素一次跨 n/2 格,接近前半段
  第二轮 gap=n/4:再跨 n/4 格,更接近首位
  ……
  每轮跨越距离指数衰减,总挪动 ≈ n/2 + n/4 + … ≈ O(n)
  但这是「单个元素」的代价;n 个元素分摊后整体降到 O(n^1.3)

直观地讲,希尔排序把「排序」这件事分层了:先用少量大跨步消除「远距离逆序」(这些逆序在插入排序里要花最多步数),再用小跨步做精细调整。每一轮都在前一轮「已经较好」的数组上工作,避免了插入排序「从零开始一格一格挪」的低效。

为何不能直接证明成 O(nlogn)? 因为不同 gap 轮之间不是完全独立的——前一轮的排序结果会影响后一轮的工作量,这种耦合使得精确分析极其困难。目前最好的理论结果(如 Sedgewick 序列的 O(n^(4/3)))都依赖特定的数论性质,且仍有改进空间。

二、平均 O(n^1.3):经验与部分理论

「希尔排序平均 O(n^1.3)」这个数字需要谨慎理解:

  • 它不是严格证明的普适下界:对「任意 gap 序列」没有统一的平均复杂度结论。
  • 它是特定好序列的经验值和上界:对 Sedgewick、Knuth 等序列,实验显示平均比较次数约 O(n^1.25) ~ O(n^1.33),已证明的最坏上界也在这个量级。
  • 1.3 这个指数:介于 O(n)(线性)和 O(n²)(二次)之间,比 O(nlogn)(≈O(n^1.1~1.2),因 logn 增长极慢)略差,但比插入排序的 O(n²) 好得多。

实践上,对于 n 在几千到几万的随机数据,希尔排序(尤其用 Sedgewick 序列)的实测速度常常优于堆排序(堆排虽然大 O 是 O(nlogn),但常数大、缓存不友好),甚至接近随机化快排——这也是它「理论上不如 O(nlogn),工程上不输」的原因。

三、最好情况:O(nlog²n) ~ O(nlogn)

当输入已经有序时,希尔排序每一轮的内层插入排序都会立即 break:

a[j-gap] > tmp  对所有 j 都不成立(数组有序)
→ while 循环一次都不进入
→ 每个元素只比较 1 次(而非挪动多次)

总比较次数 = n × gap 的轮数。对于每次减半的 gap 序列,轮数约 log₂ n;对于 Knuth 序列(3 倍关系),轮数约 log₃ n。所以最好情况约 O(nlogn)(更宽松地说 O(nlog²n))。这与插入排序最好 O(n) 相比略差(多了一层 gap 轮),但因为每轮都极快,实际开销很小。

四、不稳定:分组破坏相对次序

希尔排序固有地不稳定。原因在于分组:两个值相等的元素若下标差不是当前 gap 的倍数,就会被分到不同子序列,各自独立排序时它们的相对次序无法保证——一个可能被跨段交换到另一个之后。

例子:a = [3a, 2, 3b],gap=2
组 0(下标 0,2):[3a, 3b](相等,保持 3a 在前)
组 1(下标 1):  [2]
但若某轮 gap 的交换把 3b 移到 3a 之前,相对次序就被打乱

这与归并排序的「不稳定」不同——归并的不稳定是「比较符号写错」导致的(写成 < 而非 <=,可修复);希尔的不稳定是分组机制本身导致的,无法靠代码写法修复。所以需要稳定排序的场景(数据库多关键字排序、先按 A 排再按 B 排)不能用希尔排序,要用归并或 Timsort。

五、为何中等规模实用:常数小、原地、无递归

虽然希尔排序渐近复杂度(O(n^1.3))不如快排/归并/堆排(O(nlogn)),但在中等规模数据(n 为几百到几千)上,它有几个工程优势:

维度希尔排序堆排序快速排序
渐近复杂度O(n^1.3)O(nlogn)O(nlogn)
常数因子大(跨层跳跃)❌小 ✅
缓存友好顺序扫描差(树形跳跃)❌顺序分区 ✅
额外空间O(1)O(1)O(logn)(栈)
递归(迭代)✅无/有有(栈深风险)
实现难度极简中(pivot/分区)

在 n 不大时,O(nlogn) 和 O(n^1.3) 的差异尚未拉开,而希尔排序的小常数 + 缓存友好 + 无递归 + 极简实现优势凸显——实测常常快于堆排,接近快排。这就是为什么许多库排序(如旧的 uclibc、部分嵌入式实现)对小数组或作为兜底选用希尔排序的原因。数据量再大(n 过万),渐近劣势显现,就该切到快排/归并了。

六、与各排序横向对比

算法平均最坏最好空间稳定备注
希尔排序~O(n^1.3)O(n²)~O(n^1.5)(序列)O(nlogn)O(1)原地、无递归、中等规模快
插入排序O(n²)O(n²)O(n)O(1)近乎有序极快,小数组兜底
快速排序O(nlogn)O(n²)O(nlogn)O(logn)实际最快,原地
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定、最坏可控
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)原地、最坏可控,常数大

一句话总结:希尔排序是「插入排序的工程增强版」——用 gap 分组换取了突破 O(n²) 的能力,在中等规模上以极简代码跑出接近快排的速度,是不需要稳定性、追求简单高效的实用之选。

交互演示

下一步

理解了性能分析后,下一步是查阅参考——完整的复杂度表(按 gap 序列细分)、各 gap 序列代码模板、与插入/快排对比的速查,以及常见易错点,见参考