算法原理
希尔排序是一种基于插入排序的排序算法,也被称为缩小增量排序。它通过将待排序的序列分割成若干个子序列,对每个子序列进行插入排序,然后逐步缩小增量,最终使整个序列有序。
算法描述
希尔排序(Shell Sort)是一种基于插入排序的算法,由Donald Shell于1959年提出。它是插入排序的一种更高效的改进版本。希尔排序的核心思想是将原始数据分成多个子序列,先对每个子序列进行插入排序,然后逐步减少子序列的数量,最终对整个数据序列进行插入排序。
这个算法的关键特点是其间隔序列的选择,即每次排序时元素之间的间隔。开始时,间隔较大,随着算法的进行,间隔逐渐减小,最后间隔为1,即普通的插入排序。通过这种方式,希尔排序可以快速减少大量的初期乱序,从而提高排序效率。
希尔排序的步骤如下:
- 选择合适的间隔序列:最初的版本使用序列长度的一半作为第一个间隔,然后逐步减半,直到间隔为1。现代版本可能会使用不同的间隔序列。
- 分组并排序:按照当前间隔将数组分为多个子序列,独立地对每个子序列应用插入排序。
- 减少间隔并重复:减少间隔大小,重复上述过程,直到间隔减至1。
- 最终排序:当间隔减至1时,进行一次标准的插入排序,此时由于序列已经部分排序,这一步骤会非常快。
动画演示
代码实现
public static void shellSort(int arr[]) {
int n = arr.length;
//设置排序的间隔(gap)。
//初始间隔设置为数组长度的一半,每次循环后间隔减半。
//循环继续直到间隔减至0。
for (int gap = n / 2; gap > 0; gap /= 2) {
//内层循环从gap开始,对数组进行遍历。
for (int i = gap; i < n; i += 1) {
int temp = arr[i];//存储当前元素的值。
int j;//用于后续的内嵌循环,用于比较和交换元素。
//此循环用于将较大的元素向右移动。
//当j大于等于gap且arr[j - gap](当前元素的前一个间隔位置的元素)大于temp(当前元素)时,将arr[j - gap]向右移动到arr[j]的位置。
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
//此处的j 是上面循环 j -= gap 后的。
arr[j] = temp;
}
}
}
算法复杂度
时间复杂度(最坏) | 时间复杂度(最好) | 时间复杂度(平均) | 空间复杂度 | 稳定性 |
O(n^2) | O(nlogn) | O(n^1.5) | O(1) | 不稳定 |
- 最好情况:O(nlogn)
- 平均情况:取决于间隔序列。对于原始的间隔序列(减半序列),平均时间复杂度为O(n^1.5)。
- 最坏情况:取决于间隔序列。对于某些不佳的间隔序列,最坏情况可以达到O(n^2)。
- 空间复杂度:O(1)。希尔排序是一种原地排序算法。
希尔排序的优化方式
<<<前方高能 量力而行>>>
希尔排序的性能在很大程度上依赖于间隔序列的选择。不同的间隔序列会导致算法的性能有显著差异。优化希尔排序通常涉及使用更有效的间隔序列,而不是初始版本中的简单递减序列(如数组长度的一半递减至1)。以下是几种常见的优化方法:
- 优化间隔序列
- Hibbard序列:使用形式为 2^k - 1 的间隔,如 1, 3, 7, 15, 31, … 这样的序列可以使希尔排序达到大约 O(n^(3/2)) 的时间复杂度。
- Sedgewick序列:这是一种更复杂的间隔序列,可以进一步优化性能。一个常见的Sedgewick序列是 1, 5, 19, 41, 109, …
- Knuth序列:使用形式为 (3^k - 1) / 2 的间隔,如 1, 4, 13, 40, 121, …
这些序列都是试图在排序过程中更有效地打乱数组,从而避免某些特定情况下的糟糕性能。
- 动态间隔
一种更高级的优化是根据数组的特定特征动态选择间隔。这种方法通常适用于特定类型的数据或在需要极致优化时使用。
下面是一个使用Hibbard间隔序列的希尔排序的Java实现示例:
public static void sort(int[] arr) {
int n = arr.length;
int h = 1;
// 计算最大间隔
while ((h * 2) - 1 < n) {
h = h * 2;
}
h = h - 1;
// 希尔排序
while (h >= 1) {
for (int i = h; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= h && arr[j - h] > temp; j -= h) {
arr[j] = arr[j - h];
}
arr[j] = temp;
}
h = (h + 1) / 2 - 1; // 更新间隔为下一个较小的Hibbard数
}
}
这段代码首先计算最大的Hibbard间隔,然后在每次迭代中减小间隔,直到间隔为1。每个间隔都会对数组进行一轮希尔排序。这种使用Hibbard间隔的方法通常比简单的递减间隔方法表现得更好。
相关概念
• 稳定
:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
• 不稳定
:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
• 时间复杂度
:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
• 空间复杂度
:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。