【挑战程序设计竞赛 | 笔记】希尔排序:shellSort

28 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 29 天,点击查看活动详情

希尔排序 | shellSort

思想

希尔排序法,也称为缩小增量排序,是插入排序算法的改进版本。我们知道,插入排序法可以高速处理顺序较整齐的数据,而希尔排序法就是充分发挥这一特点

具体的,希尔排序算法记录如下:

  • 把记录按下标的一定增量 g 进行分组,并对每组使用插入排序算法进行排序;
  • 随着增量 g 逐渐减少,每组包含的数值会越来越多
  • 当增量 g 减到 1 时,全部数据将被分为一组。此时再进行一次插入排序即可。

理解

这里的增量以及分组该如何理解?往下看:

  • 假设现在我们需要通过希尔排序法将数组 nums 中的元素按升序的形式排列。
  • nums = [2, 7, 9, 4, 5, 6, 3, 1]
  • 通常的,我们会将增量 g 初始化为数组 nums 长度的一半,即 g = nums.size() / 2
  • 因此,这里的 g 等于 4。
  • 接着,我们根据增量 4 对数组进行分组(也就是将相隔距离为 4 的元素分为一组)。
  • 因此在数组[2, 7, 9, 4, 5, 6, 3, 1] 中,2 和 5分为一组,7 和 6 分为一组,9 和 3 分为一组,4 和 1 分为一组。
  • 对每一组分别进行插入排序,那么数组更新为:[2, 6, 3, 1, 5, 7, 9, 4](比如 9 和 3 是一组,所以交换二者的位置)
  • 当我们对每个组分别进行插入排序后,就可以来更新增量 g
  • 一般更新增量 g 的方式为 g /= 2;,因此此时的 g 为 2。
  • 此时,我们根据增量 2 对数组进行分组。
  • 因此在数组[2, 6, 3, 1, 5, 7, 9, 4] 中,2, 3, 5, 9分为一组,6, 1, 7, 4分为一组
  • 对每一组分别进行插入排序,那么数组更新为:[2, 1, 3, 4, 5, 6, 9, 7]
  • 现在的任务就是更新增量 g ,即 g /= 2;,所以 g 更新为 1。
  • 将相隔距离为 1 的元素组成一组(其实就只有一组了)
  • 用插入排序对该组进行排序,此时,排序已经结束了。

代码

void shellSort(int A[], int size) {
    int g, pos, j;  // g 为希尔增量,pos为待插入记录位置
    int tmp;
    for (g = size/2; g > 0; g /= 2){        
        for (pos = g; pos < size; pos++) {
         tmp = A[pos];
            for (j = pos - g; j >= 0 && A[j] > tmp; j -= g) 
                A[j+g] = A[j];  // 记录后移 
            A[j+g] = tmp;       // 将待插入记录放到合适位置
        }
    }
}

分析

要想完成数据排序就必须在最后指向 g = 1,使用插入排序法。(此时对象数据的顺序应该已经很整齐了)

另外,增量 g 的选择方法数不胜数,迄今为止人们已经对其进行了许多研究。这里记录另一个增量取值方式:当 gn+1=3gn+1g_{n+1} = 3g^n + 1 时,算法的复杂度基本维持在 O(N1.25)O(N^{1.25})