我们之前学过插入排序,它在数组大部分元素已经有序时效率很高,但如果数组完全无序或者逆序,每次只能移动一个元素,效率就比较低。希尔排序(Shell Sort)是插入排序的改进版,它通过分组的方式,让数组先变得基本有序,再用插入排序处理,从而提高效率。

希尔排序的基本思想

希尔排序的核心是“分组”和“插入排序”。它不像插入排序那样只比较相邻元素,而是将数组分成若干组,每组内的元素间隔一定的距离(这个距离称为“增量”或“步长”),然后对每组内的元素进行插入排序。随着增量逐渐减小,分组越来越细,直到增量为1时,整个数组已经基本有序,此时插入排序就能快速完成。

希尔排序的步骤

  1. 选择初始增量:通常初始增量取数组长度的一半(n//2),然后每次将增量减半,直到增量为0。
  2. 分组与插入排序:对于每个增量gap,数组被分成gap个“组”,每个组内的元素下标相差gap。例如,数组长度为10,gap=3时,组1是下标0、3、6、9,组2是下标1、4、7,组3是下标2、5、8。然后对每组内的元素进行插入排序。
  3. 重复调整增量:当增量减小到1时,整个数组已基本有序,此时插入排序能快速完成。

代码实现(Python)

def shell_sort(arr):
    n = len(arr)
    gap = n // 2  # 初始增量为数组长度的一半
    while gap > 0:
        # 遍历每个组内的元素
        for i in range(gap, n):
            temp = arr[i]  # 保存当前元素,避免被覆盖
            j = i - gap    # 组内前一个元素的下标
            # 将前面比temp大的元素后移
            while j >= 0 and arr[j] > temp:
                arr[j + gap] = arr[j]  # 后移一位
                j -= gap               # 继续向前比较
            arr[j + gap] = temp        # 将temp插入到正确位置
        gap = gap // 2  # 增量减半,逐步缩小
    return arr

代码解释
- n = len(arr):获取数组长度,用于计算初始增量。
- gap = n // 2:初始增量为数组长度的一半,这是最常用的简单增量方式。
- while gap > 0:循环处理不同的增量,直到增量为0。
- for i in range(gap, n):遍历每个组内的元素(从第gap个元素开始)。
- temp = arr[i]:保存当前元素,避免后续赋值覆盖。
- j = i - gap:计算当前元素所在组的前一个元素下标。
- while j >= 0 and arr[j] > temp:若前一个元素比temp大,后移该元素并继续向前比较;否则停止。
- arr[j + gap] = temp:找到合适位置后,将temp插入。
- gap = gap // 2:增量减半,逐步缩小分组,直到分组为1(即普通插入排序)。

举例说明(以数组[5, 3, 8, 4, 2]为例)

  1. 初始数组[5, 3, 8, 4, 2]n=5,初始增量gap=5//2=2

  2. 处理gap=2
    - 分组:组1(下标0、2、4:元素5、8、2),组2(下标1、3:元素3、4)。
    - 组1插入排序

    • i=2(元素8):j=0i-gap=0),arr[0]=5 <=8,无需移动。数组不变:[5,3,8,4,2]
    • i=4(元素2):j=4-2=2arr[2]=8>2),后移8到arr[4],数组变为[5,3,2,4,8]j=0arr[0]=5>2),后移5到arr[2],数组变为[5,3,5,4,8]j=-2(找到位置),插入2到arr[0],组1排序后:[2,3,5,4,8]
    • 组2插入排序
    • i=3(元素4):j=3-2=1arr[1]=3 <=4),无需移动。数组不变:[2,3,5,4,8]
  3. 处理gap=1(此时增量减半为1,即普通插入排序):
    - i=1(元素3):j=0arr[0]=2 <=3),不动。
    - i=2(元素5):j=1arr[1]=3 <=5),不动。
    - i=3(元素4):j=2arr[2]=5>4),后移5到arr[3],数组变为[2,3,5,5,8]j=1arr[1]=3 <=4),插入4到arr[2],数组变为[2,3,4,5,8]
    - i=4(元素8):j=3arr[3]=5 <=8),不动。

  4. 最终结果[2, 3, 4, 5, 8]

总结

  • 时间复杂度:平均约为O(n log n),最坏情况为O(n²),比插入排序更高效。
  • 空间复杂度:O(1),原地排序,仅需临时变量。
  • 适用场景:适用于中等规模数组,尤其当元素分布不均匀时,分组排序可减少移动次数。

通过分组和逐步缩小增量,希尔排序让数组先“粗排”再“精排”,有效提升了插入排序的效率,是一种实用的排序算法。

小夜