什么是希尔排序?
其实,希尔排序主要就是分组插入,即将一组数据分组然后使用插入排序来实现的。主要分为两步走:1.分组直接插入预排序 2.分组直接插入预排序结束后的整体直接插入排序
经过这两步之后,其数据也就排序好了。
Shell排序的效率其实很高的,尤其在对大量数据进行排序的时候就容易体现出来了。
分组直接插入预排序
那么,我们首先该如何来先对一组数据进行分组呢?
假设,我们想将这组数据分成三组,设gap = 3;
那么,接下来我们可以同时将间距为gap的作为一组数据。其每组数据的起始位置分别是数组下标为0,1,2这三个。
如下图:
这样,我们就迈出了第一步了,将数据分好组了。
分别为:
黑色:7 9 8 3
红色:10 2 6
蓝色:1 5 4
接下来就是插入预排序了。
这里,我们先以黑色的一组数据也就是7 9 8 3先来作为一组数组来对其进行直接插入排序
关于直接插入排序的博客请参考上一篇博客:C语言】直接插入排序_吃饭爱喝水的博客-CSDN博客
对黑色一组数据进行直接插入排序的核心代码:
(****end的最初位置在数组下标为6的位置上,并且 n-gap的值就是end能取到的的最大值)
int gap;
//a[0]~a[end]有序,把数组下标为end + 1的值插入数组后,数组仍保持有序。
int end;
int tmp = a[end + gap];
while (end >= 0) {
if (tmp < a[end]) {
a[end + 1] = a[end];
end -= gap;
}
else {
break;
}
}
a[end + gap] = tmp;
最终结果如下图:
剩下的两个红色一组和蓝色一组的数据的排序和黑色这一组类似,就不过多重复了。
三组数据分别插入排序整体代码:
void ShellSort(int* a, int n) {
int gap = 3;
//对分成的gap组数据的分别插入排序
for (int i = 0; i < gap; i++) {
//对其中一组数据的直接插入排序
for (int j = 0; j < n - gap; j += gap) {//***当gap等于1时,就是一个直接插入排序
// n-gap 就是end能取到的的最大值
//a[0]~a[end]有序,把数组下标为end + gap的值插入数组后,数组仍保持有序。
int end = j;
int tmp = a[end + gap];
while (end >= 0) {
if (tmp < a[end]) {
a[end + 1] = a[end];
end -= gap;
}
else {
break;
}
}
a[end + gap] = tmp;
}
}
}
三组数据交替插入排序整体代码(与三组数据分别插入排序整体代码产生最终结果一致):
for (int j = 0; j < n - gap; j++){
int end = j;
int tmp = a[end + gap];
while (end >= 0) {
if (tmp < a[end]) {
a[end + 1] = a[end];
end -= gap;
}
else {
break;
}
}
a[end + gap] = tmp;
}
三组数据都分别直接插入排序最终结果为:(分组插入预排序的最终结果 )
3,2,1,7,10,4,9,6,5,8.
这就是分组插入预排序的最终结果。
分组直接插入预排序结束后的整体直接插入排序
排升序的时候,gap的值如果越大,那么其大的值会很快到后面去,小的值也同样会很快的到前面来。但是这样的话,会导致预排序的结果也就越混乱
gap的值越小,预排序的结果混乱度也就越小,越接近有序。
并且当gap等于1时,也就是直接插入排序了。
因此,我们可以将其代码写为:
void ShellSort(int* a, int n) {
int gap = n;
//当 gap>1时,就是对数据进行了分组预排序
//当gap ==1时,就是对分组预排序的结果进行直接插入排序了
while (gap >= 1) {
gap = (gap / 3) + 1;//除以三是为了使gap的值不至于太大从而导致预排序的结果比较混乱
//并且最后加1是为了确保最后 gap == 1 ,从而进行最后的直接插入排序
//对分成的gap组数据的分别插入排序
for (int i = 0; i < gap; i++) {
//对其中一组数据的直接插入排序
for (int j = 0; j < n - gap; j += gap) {//***当gap等于1时,就是一个直接插入排序
// n-gap 就是end能取到的的最大值
//a[0]~a[end]有序,把数组下标为end + gap的值插入数组后,数组仍保持有序。
int end = j;
int tmp = a[end + gap];
while (end >= 0) {
if (tmp < a[end]) {
a[end + 1] = a[end];
end -= gap;
}
else {
break;
}
}
a[end + gap] = tmp;
}
}
}
}
这里,我们就可以将上面的:
3,2,1,7,10,4,9,6,5,8.
分组插入预排序的最终结果
其实,这个部分也就是gap>1时循环的最终结果。
接下来,当gap循环到成为1 时,就将这组数据进行直接插入排序,从而完成整个数据的排序。
到这,Shell排序就完成了。
简单小结:
其实,Shell排序就分为两步走。
首先,先对整个数剧分为gap组。分组完了后,再分别对每一组数据进行直接插入排序。完成后,数据整体上就转成为了一个相对有序的数据了。当然,还是有些个地方不是有序的。
然后,这时,再对其进行最后一次,也就是gap == 1时,进行直接插入排序。从而使整个数据转成为有序了。到这时,Shell排序也就完成了。