写希尔排序注意:
写新元素融入有序数组的过程(end&tmp) 将这个过程给多次类比到需要排序的一串数据中 (for&while) 排完一组不够,需要排gap组 (再来for) 敲定gap 下标关系:
希尔排序与直接插入排序的区别与联系
希尔排序的话也叫做缩小增量插入排序 他其实也是一种插入排序,只不过是在原先那种朴素的插入排序的基础之上给他优化了一下而已。 因为像之前就已经知道这个插入排序的话最坏的情况,时间复杂度为O(N^2),然后最好的情况的话,时间复杂度就是O(N)。这边就可以发现一个规律,就是说如果说你原先这个数组越接近顺序有序的话,插入排序就越快。然后希尔排序的话就是借助于这一点。他一上来的话不直接进行插入排序,而是先进行一下预排序。 怎么个预排序法呢?就是说分组插排。这个预排序的目的就是说先让这个数组尽可能的接近于顺序有序,这样子的话,等会儿下一步进行插入排序的话就会快不少。
预排序
预排序的话就是将总数据先按间隔(gap-1)被分成gap组。 (gap为1的时候其实就是直接插入排序) 关于这个“gap”里面蕴含的数量关系 1. 被分成gap组。 2. 每组的成员下表只要再加上gap就会跳到下一个同组成员(当然也是同一组的)。 3. 在每组当中,成员与成员之间隔着gap-1个元素。 然后对这个每一组进行单独的插入排序(此时每一组之间的元素都是不连续的,他们之间的间隔是gap-1)。 这样子预处理完了之后,整个数组看起来的话就会更加的接近于顺序有序。因为比如说我需要升序,然后最大的一个数在数组的开头,如果说按原先直接插入排序的话,这个最大的数是一步一步向后挪的。然后如果我进行这个预排序优化的话,它挪动的速度可以变得更加快,因为他现在一次的话挪动gap步(在预排序阶段)。 在最开始的话,还是把插入排序那个抽象出来的过程先给他代码写出来,但这边的话又与直接插入排序是有一定的区别的。道理还是一样的,这个end表示已经有序的数组的右边疆下标,然后这个tmp表示即将要插入到这个有序数组的数值
int gap;
int end;
int tmp ;
while ( end >= 0 )
{
if ( arr[ end] > tmp)
{
arr[ end + gap] = arr[ end] ;
end -= gap;
}
else
{
break ;
}
}
arr[ end + gap] = tmp;
然后上面这段代码仅仅表示当有一个数据想要新融入到已经有序的数组当中的一个过程, 然后再把这个过程给多次类比到你需要排序的一串数据当中,于是代码如下:
int gap;
for ( int i = 0 ; i < n - gap; i += gap)
{
int end = i;
int tmp = arr[ end + gap] ;
while ( end >= 0 )
{
if ( arr[ end] > tmp)
{
arr[ end + gap] = arr[ end] ;
end -= gap;
}
else
{
break ;
}
}
arr[ end + gap] = tmp;
}
然后之前不是提到在预处理阶段有分成了gap组数据嘛,上面的代码只是对一组数据进行了直接插入排序预处理,然后接下来就是把之前所有分好的gap组数据全部都直接插入排序一下:
int gap;
for ( int j = 0 ; j < gap; j++ )
{
for ( int i = j; i < n - gap; i += gap)
{
int end = i;
int tmp = arr[ end + gap] ;
while ( end >= 0 )
{
if ( arr[ end] > tmp)
{
arr[ end + gap] = arr[ end] ;
end -= gap;
}
else
{
break ;
}
}
arr[ end + gap] = tmp;
}
}
gap的确定与预排序完最终直接插入排序
gap越大,相当于就是说他跳的越快,这样子的话就越不接近有序。但是也有好处,就是说如果大了的话,就可以尽快的向后跳。反之也一样。 那到底这个gap到底是取多少比较合适?实际上的话,这个gap的话在希尔排序当中也是在不断发生变化的,并不是定死的。
void ShellSort ( int * arr, int n)
{
int gap = n;
while ( gap > 1 )
{
gap /= 2 ;
for ( int j = 0 ; j < gap; j++ )
{
for ( int i = j; i < n - gap; i += gap)
{
int end = i;
int tmp = arr[ end + gap] ;
while ( end >= 0 )
{
if ( arr[ end] > tmp)
{
arr[ end + gap] = arr[ end] ;
end -= gap;
}
else
{
break ;
}
}
arr[ end + gap] = tmp;
}
}
}
}
注意看上面的while循环结束的条件,当这个gap>1的时候就进入到这个循环内部,然后注意看这个while循环里面的第一条语句,先把这个gap/2,这样子无论如何的话,最终必须有一次这个gap肯定是一(也就变成了直接插入排序)。当然也不一定需要,这样也可以把gap/3+1,为什么这边需要加上一呢?这是因为一定一定要在最后一次,在预排序完之后再进行一次直接插入排序,直接插入排序的话就相当于gap为1,因此在gap为1的情形下必须要去走一遭。但是当把这个gap除以三之后可能变成0了,为了确保他gap必须要有一个1, 所以说需要加1.
具体代码示例
void PrintArray ( int * arr, int n)
{
for ( int i = 0 ; i < n; i++ )
{
printf ( "%d " , arr[ i] ) ;
}
printf ( "\n" ) ;
}
void ShellSort ( int * arr, int n)
{
int gap = n;
while ( gap > 1 )
{
gap /= 2 ;
for ( int j = 0 ; j < gap; j++ )
{
for ( int i = j; i < n - gap; i += gap)
{
int end = i;
int tmp = arr[ end + gap] ;
while ( end >= 0 )
{
if ( arr[ end] > tmp)
{
arr[ end + gap] = arr[ end] ;
end -= gap;
}
else
{
break ;
}
}
arr[ end + gap] = tmp;
}
}
}
}
int main ( )
{
int arr[ ] = { 1 , 2 , 5 , 3 , 6 , 7 , 9 , 1 , 2 , 4 , 0 , 9 , 3 , 2 , 12 , 88 , 32 , 10 , 9 } ;
PrintArray ( arr, sizeof ( arr) / sizeof ( int ) ) ;
ShellSort ( arr, sizeof ( arr) / sizeof ( int ) ) ;
PrintArray ( arr, sizeof ( arr) / sizeof ( int ) ) ;
return 0 ;
}