希尔排序

希尔排序是将元素进行分组,组的大小逐渐扩大,然后对每个分组利用直接插入排序算法使分组内有序。当组的大小刚好为 n 的时候,再调用直接插入排序就可以使得整个数组有序了。

分组

如何描述组的大小变化呢?利用一个增量 dk 描述。当组逐渐扩大的时候,dk 就会逐渐减小,例如我们可以用如下 for 循环来描述 dk 的变化:

for (int dk = n / 2; dk >= 1; dk /= 2) {
	// 直接插入排序
}

对于 dk 的唯一的要求就是最后一次的循环 dk 一定为 1.

当我们确定了 dk 后,分组就唯一确定了。例如对于一个长度为 10,dk = 3 的数组来说,分组就为:

[0,3,6,9]
[1,4,7]
[2,5,8]
==========
[3,6,9]
[4,7]
[5,8]
[6,9]

注意,上述中[3,6,9]等都是没有必要的分组,因为这些分组都是等号上面分组的子分组,已经是有序的了。例如,[3,6,9]是[0,3,6,9]的子分组,[3,6,9]分组已经在[0,3,6,9]后,必定是有序的。

直接插入排序

在确定分组后,我们要做的就是对分组内的元素进行排序。

	for (int i = dk; i <= n; i++) {
	    int t = a[i], j = i;
	    while (j >= dk && a[j - dk] > t) {
	        a[j] = a[j - dk];
	        j -= dk;
	    }
	    a[j] = t;
	}

完整实现

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;
int n;
int a[N];

void shell_sort() {
    for (int dk = n / 2; dk >= 1; dk /= 2) {
        // insert_sort
        for (int i = dk; i < n; i++) {
            int t = a[i], j = i;
            while (j >= dk && a[j - dk] > t) {
                a[j] = a[j - dk];
                j -= dk;
            }
            a[j] = t;
        }
    }
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    
    shell_sort();
    for (int i = 0; i < n; i++) cout << a[i] << " ";
    return 0;
}