希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
原始的算法实现在最坏的情况下需要进行O(n2)的比较和交换。V. Pratt的书对算法进行了少量修改,可以使得性能提升至O(n log2 n)。这比最好的比较算法的O(n log n)要差一些。
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
一个更好理解的希尔排序实现:将数组列在一个表中并对列排序(用插入排序)。重复这过程,不过每次用更长的列来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身仅仅对原数组进行排序(通过增加索引的步长,例如是用i += step_size
而不是i++
)。
#include
void ShellSort(int a[], int n)
{
int gap, i, j, temp;
//gap用来控制移动距离(由大到小,直到0)
for (gap = n / 2; gap > 0; gap /=2)
{
//这里i定义为每组数据的第二个,默认第一个为有序
//i < n : 由于以下for循环每次对每组数据只做一次插入排序(即将第二个数据插入)
//(曾考虑i只需要到后一个gap即可停止循环,则结果是从第三个数开始都不会执行排序)
for (i = gap; i < n; i++)
{
temp = a[i];
//这里将简单插入排序浓缩
for (j = i - gap; j >= 0 && a[j] > temp; j -= gap)
a[j + gap] = a[j];
a[j + gap] = temp;
}
}
}
int main()
{
int a[] = {4, 2, 7, 5, 3, 8, 9, 0, 6, 1};
//int a[] = {13, 14, 94, 33, 82, 25, 59, 94, 65, 23, 45, 27, 73, 25, 39, 10};
//int a[] = {7, 7, 0, 0, 3, 0, 6, 6, 3};
ShellSort(a, 10);
for (int i = 0; i < 10; i++)
printf("%d\t", a[i]);
printf("\n");
return 0;
}
附:看到某书上给出的希尔排序编码如下
void ShellSort(int a[], int left, int right)
{
int len = right - left + 1;
int d = len;
while (d > 1)
{
d = (d + 1) / 2;
for (int i = left; i < right + 1 - d; i++)
{
if (a[i + d] < a[i])
{
int tmp = a[i + d];
a[i + d] = a[i];
a[i] = tmp;
}
}
}
}
//参数不一样,思想一样
void shell_sort(int a[], int n)
{
int d = n;
while (d > 1)
{
d = (d + 1) / 2;
for (int i = 0; i < n - d; i++)
{
if (a[i + d] < a[i])
{
int tmp = a[i + d];
a[i + d] = a[i];
a[i] = tmp;
}
}
}
}
看起来写的不错,代码比较简洁,对吗?测试数据
{4, 2, 7, 5, 3, 8, 9, 0, 6, 1};结果如下图:
倒是挺像交换排序,因为它每次比较的是前后两个数,而并非是插入一个数并完成排序。