在计算机科学的浩瀚海洋中,排序算法如同璀璨的星辰,各自闪耀着独特的光芒。希尔排序(Shell Sort)便是其中一颗别具特色的星星,它是插入排序的一种改进版本,也被称为缩小增量排序。下面我们就来深入探究希尔排序的基本思想、原理以及它的独特魅力。
在了解希尔排序之前,我们先回顾一下插入排序。插入排序的基本思想是将一个数据插入到已经排好序的序列中,从而得到一个新的、长度加一的有序序列。它就像我们整理扑克牌一样,每次拿到一张新牌,都将其插入到手中已经排好顺序的牌中合适的位置。
然而,插入排序在处理大规模数据时效率较低。因为它每次只能移动一个元素,当数据量很大时,需要进行大量的元素移动操作。为了改进这一缺陷,计算机科学家唐纳德·希尔(Donald Shell)在 1959 年提出了希尔排序算法。
希尔排序的核心思想是通过设置一个增量序列,将原始数据分成若干个子序列,对每个子序列分别进行插入排序。随着增量的逐渐减小,子序列的长度逐渐增加,整个序列会变得越来越接近有序。当增量最终减至 1 时,整个序列就被当作一个子序列进行最后一次插入排序,此时由于序列已经基本有序,插入排序的效率会大大提高。
简单来说,希尔排序就是先对相隔较远的元素进行比较和交换,让元素快速移动到大致正确的位置,然后逐步缩小间隔,直到最终完成排序。
下面我们通过一个具体的例子来详细说明希尔排序的过程。假设我们有一个待排序的数组:[9, 8, 3, 7, 5, 6, 4, 1]
。
常见的增量序列选择方法有希尔增量(初始增量为数组长度的一半,然后每次减半)、Hibbard 增量等。这里我们使用希尔增量。
数组长度为 8,初始增量 gap = 8 / 2 = 4
。
将数组分为 4 个子序列:[9, 5]
、[8, 6]
、[3, 4]
、[7, 1]
。
对每个子序列分别进行插入排序:
[9, 5]
排序后为 [5, 9]
[8, 6]
排序后为 [6, 8]
[3, 4]
排序后为 [3, 4]
[7, 1]
排序后为 [1, 7]
此时数组变为:[5, 6, 3, 1, 9, 8, 4, 7]
将数组分为 2 个子序列:[5, 3, 9, 4]
、[6, 1, 8, 7]
。
对每个子序列分别进行插入排序:
[5, 3, 9, 4]
排序后为 [3, 4, 5, 9]
[6, 1, 8, 7]
排序后为 [1, 6, 7, 8]
此时数组变为:[3, 1, 4, 6, 5, 7, 9, 8]
此时增量为 1,对整个数组进行插入排序,排序后数组变为:[1, 3, 4, 5, 6, 7, 8, 9]
轮数 | 增量 | 子序列 | 排序后数组 |
---|---|---|---|
1 | 4 | [9, 5] 、[8, 6] 、[3, 4] 、[7, 1] |
[5, 6, 3, 1, 9, 8, 4, 7] |
2 | 2 | [5, 3, 9, 4] 、[6, 1, 8, 7] |
[3, 1, 4, 6, 5, 7, 9, 8] |
3 | 1 | 整个数组 | [1, 3, 4, 5, 6, 7, 8, 9] |
def shell_sort(arr):
n = len(arr)
# 初始增量为数组长度的一半
gap = n // 2
while gap > 0:
for i in range(gap, n):
temp = arr[i]
j = i
# 对每个子序列进行插入排序
while j >= gap and arr[j - gap] > temp:
arr[j] = arr[j - gap]
j -= gap
arr[j] = temp
# 增量减半
gap //= 2
return arr
# 测试
arr = [9, 8, 3, 7, 5, 6, 4, 1]
sorted_arr = shell_sort(arr)
print(sorted_arr)
希尔排序通过引入增量序列,巧妙地改进了插入排序的效率。它在处理中等规模的数据时表现出色,尤其是在数据基本有序的情况下,效率会更高。虽然希尔排序的时间复杂度分析比较复杂,但它的思想简单易懂,实现也相对容易。在实际应用中,希尔排序是一种值得考虑的排序算法。