1. 引言
在本教程中,我们将介绍 Java 中的 希尔(shell)排序算法。
2. 希尔排序概述
2.1. 描述
让我们首先描述 希尔排序算法,以便我们知道我们要实现什么。
希尔排序基于 Insertion 排序算法,属于一组非常高效的算法。通常,该算法将原始集合分解为更小的子集,然后使用插入排序对每个子集进行排序。
但是,它如何制作子集并不简单。它不会像我们预期的那样选择相邻元素来形成一个子集。相反,shell 排序使用所谓的间隔或间隙来创建子集。例如,如果我们有间隙 I,则意味着一个子集将包含 I 位置相隔的元素。
首先,该算法对彼此相距较远的元素进行排序。然后,间隙变小,并比较更接近的元素。这样一来,一些不在正确位置的元素的定位速度可以比我们从相邻元素中制作子集更快。
2.2. 示例
让我们在示例中看到这一点,其中有 3 和 1 的间隙以及 9 个元素的未排序列表:
如果我们按照上面的描述,在第一次迭代中,我们将有三个子集,其中包含 3 个元素(以相同的颜色突出显示):
在第一次迭代中对每个子集进行排序后,列表将如下所示:
我们可以注意到,虽然我们还没有排序列表,但这些元素现在更接近所需的位置。
最后,我们需要再做一个以 1 为增量的排序,它实际上是一个基本的插入排序。现在,我们需要执行的对列表进行排序的移位操作的数量比我们不进行第一次迭代时的情况要少:
2.3. 选择间隙序列
正如我们所提到的,壳排序具有选择间隙序列的独特方式。这是一项艰巨的任务,我们应该注意不要选择太少或太多的差距。更多详细信息可以在最建议的间隙序列列表中找到。
3. 实施
现在让我们看一下实现。我们将使用 Shell 的原始序列进行间隔增量:
N/2, N/4, …, 1 (continuously dividing by 2)
实现本身并不太复杂:
public void sort(int arrayToSort[]) {
int n = arrayToSort.length;
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int key = arrayToSort[i];
int j = i;
while (j >= gap && arrayToSort[j - gap] > key) {
arrayToSort[j] = arrayToSort[j - gap];
j -= gap;
}
arrayToSort[j] = key;
}
}
}
我们首先创建了一个带有 for 循环的间隙序列,然后对每个间隙大小进行插入排序。
现在,我们可以轻松测试我们的方法:
@Test
void givenUnsortedArray_whenShellSort_thenSortedAsc() {
int[] input = {41, 15, 82, 5, 65, 19, 32, 43, 8};
ShellSort.sort(input);
int[] expected = {5, 8, 15, 19, 32, 41, 43, 65, 82};
assertArrayEquals("the two arrays are not equal", expected, input);
}
4. 复杂性
通常,**Shell 排序算法对于中型列表非常有效。**复杂度很难确定,因为它很大程度上取决于间隙序列,但时间复杂度在 O(N) 和 O(N^2) 之间变化。
最坏情况下的空间复杂度为 O(N),辅助空间为 O(1)。
5. 结论
在本教程中,我们描述了 Shell 排序,并说明了如何在 Java 中实现它。