老城区的邮局有一面墙,上面是 100 个信格,从左到右依次对应邮编的最后两位:00、01、02、…、99。
每天早晨,邮递员把一整袋信件倒在工作台上。老局长说:「不要一封一封比大小,看编号,直接扔进对应的格子。」于是 5000 封信,每个人只看了 1 次编号,扔 1 次格子 —— 几百秒就分完了。
更妙的是:这批信根本不需要互相比较。信 A 和信 B 谁大谁小,从头到尾没人关心,大家只关心「你是几号格子的」。分完之后,把 100 个格子里的信从左到右倒出来,就是完美有序的。
第二个难题出现了:如果邮编有 6 位(1 000 000 个可能值),难道要准备 100 万个信格吗?当然不行。老局长的办法是:先按后两位分一次、收集起来;再按中间两位分一次、收集起来;最后按前两位分一次。三轮之后,所有信件完全有序。
按值分格 = 桶排序;每个值一格 = 计数排序;按位分多轮 = 基数排序。
而如果没有任何「值域」可用,只能靠比较呢?那就必须有一个能快速拿到最值的数据结构 —— 堆。用堆完成的排序就是今天的第一个主角。
💡 三条完全不同的思路
堆排序:仍然是比较排序。先把数组整理成大根堆(O(n)),再反复「取出堆顶放到末尾、堆缩小、下沉新堆顶」,共 O(n log n)。原地、不稳定。
桶排序 / 计数排序 / 基数排序:属于非比较排序。它们不比较元素的大小关系,而是利用「元素的键可以映射到有限个下标」这一额外信息,达到 O(n) 级别。
代价也很明确:只适用于值域可控的数据(整数、有限字符串),并且通常需要额外的 O(n + V) 空间。