希尔排序与 Shell 序列
看完你能亲手推演插入排序分组过程并说清Shell序列为何失效
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
希尔排序与 Shell 序列
看完你能亲手推演插入排序分组过程并说清Shell序列为何失效
插入排序的痛点
上一页我们知道了希尔排序要靠插入排序打下手。可插入排序本身有个老毛病:一个元素想挪到正确位置,每次只能和左边的邻居换一步。前面堆积的乱序越多,它就越吃力。
每挤过一个人才挪一步,前面越挤挪得越慢
希尔排序的策略
上一页我们看到:插入排序慢,是因为元素每次只能挪一小步。那让它先大步挪呢?先用大间隔把元素粗略理顺,再逐步细化,最后一步才是普通插入排序——此时数组已近乎有序。
先按大类粗分书堆,再逐层细排,最后微调
什么是 h-有序
上一页说了:希尔排序让相隔 h 的元素先排好队。那“排好队”到底怎么算数?这就需要一个精确的标准——它就是 h-有序。
每个管家只管固定楼层的房间有序,对应每条 h 间隔子序列各自有序
循秩访问:抽取子序列
看 8 个数的数组如何按间隔 h=4 被拆成隐形的子序列:元素不移动,访问时按秩跳跃。
子序列上的插入排序
不真正抽取子序列,只把插入排序的步长 1 换成 h——循秩访问即可遍历每个 h-子序列。
与普通插入排序逐行对照:唯一改动是步长从 1 变为 h,j 按秩跳 h 前进,物理上未抽取任何子序列。
实例:逐轮执行过程
用 8 个逆序元素演示 h=4、2、1 三轮排序,看每轮后的数组状态。
观察:大步换来什么
按轮次从左到右看:每轮标注单次移动的典型距离,前期大步远移,后期小步微调。
插入排序 vs 希尔排序
两者最后一轮完全一样,为何性能天差地别?关键在移动次数的分布,值得逐维对照。
- 面对全序列,步长固定为 1
- 元素可能被反复远距离搬运
- 移动次数正比于全体逆序数
- 先大步分组,多轮铺垫到 1
- 大步时一次搬运顶多步小移动
- 末轮几乎有序,移动极少
Shell 序列的定义
前页看到逐轮步长带来的加速,那步长到底按什么规律取?Shell 给出的答案很朴素:从数组长度的一半开始,每轮除以 2,一路减到 1 为止。
先大倍率粗略定位,再逐级缩小焦距精细对准
Shell 序列的短板
上一页我们看到 Shell 序列按 1、2、4、8… 逐轮折半。它简单好记,但藏着一个隐患:相邻增量全是彼此的倍数。这会让某些位置的元素长期“互相看不见”,最坏情况效率大幅退化。
箱子容量逐次减半时,零头物品始终挪不动,直到最后一箱一箱慢慢搬
自测:增量设计
使用 Shell 序列 1,2,4,8,… 的希尔排序,最核心的问题是什么?
要点收束
- ✓插入排序的性能瓶颈在于元素只能逐位挪动
- ✓希尔排序用大步先远距离搬运,小步再精修
- ✓h-有序是层层铺垫:前一轮为后一轮省工
- ✓Shell 序列实现简单,但增量间公因数削弱效果
- ✓增量设计是自由的——序列好坏决定整体复杂度
课后思考
三个问题没有唯一答案,先自己想,再看参考答案。
参考答案思考路径:回看 h-有序的定义——h-有序只约束间隔 h 的元素。步长交替互质时,新一轮排序会打破旧约束,但整体逆序数仍大幅下降;Shell 序列的公因子问题正出在这里。
参考答案思路:h-排序等价于对 h 个子序列各做插入排序,只能消灭两端距离恰为 h 倍数的逆序对;间隔不是 h 倍数的逆序对要等更小步长处理。用逆序数下降量解释大步的收益。
参考答案方向:相邻步长尽量互素、避免 2 的幂;末尾必须收敛到 1 保证正确性;经验上步长按约 2.2 倍左右递减效果较好。可对照 Hibbard、Knuth 序列验证你的原则。