排序算法(八)希尔排序(缩小增量排序)

1、前面的二路插入排序是将已有序数据分成两部分,使得后续插入操作只在其中一部分内进行,这样,缩小范围后

的查找及移动次数都会减少。推而广之,如果能将所有数据分成更多组,则每组内的数据量更小,这样,查找及移动

次数会进一步减少。希尔排序就是基于这样一种思路来设计的排序算法。


2、希尔排序(Shell Sort,又称缩小增量法)是一种分组插入排序方法,因DL.Shell于1959年提出而得名,实质上是一

种分组插入方法。


3、排序思想

(1)先取一个正整数d1(d1

每个k(k=1,2,…d1),R[k],R[d1+k],R[2d1+k],…分在同一组中,在各组内进行直接插入排序。这样一次分组和排序过程

称为一趟希尔排序;

(2)新的增量d2(1)的分组和排序操作;

(3)重复(2),直至所取的增量di等于1为止,即所有记录放进一个组中排序为止。


4、通常会以一组数据{49, 38, 65, 97, 76, 13, 27, 49, 55, 04}为例辅助说明,本文也以此为例:

(1)按照上述排序思想,需要先确定第一个增量,这里选择d1等于5,然后根据增量5将待排序数据分组如下:


原始待排序数据 49 38 65 97 76 13 27 49 55 04
第1组分组 49



13

   
第2组分组   38



27
   
第3组分组  
65



49
 
第4组分组    
97



55  
第5组分组    

76



04


(2)如上表将10个数据,根据增量5分成了5组,并且恰好每组都是2个数;然后对每组中的数据使用直接插入法排序,排序结果如下表:
原始待排序数据 49 38 65 97 76 13 27 49 55 04
第1组排序结果 13



49

   
第2组排序结果   27



38
   
第3组排序结果  
49


  65
 
第4组排序结果    
55



97  
第5组排序结果    

04



76


(3)OK,现在一趟希尔排序就完成了,排序结果为:{13, 27, 49, 55, 04, 49, 38, 65, 97, 76};

(4)重新选择一个增量3(3小于5),对第一趟希尔排序的结果重新分组:

第1趟排序结果 13 27 49 55 04 49 38 65 97 76
第1组分组 13

55
  38
  76
第2组分组   27

04
  65    
第3组分组     49

49

97

(5)对3个分组分别进行排序,结果如下:

第1趟排序结果 13 27 49 55 04 49 38 65 97 76
第1组排序结果 13

38
  55
  76
第2组排序结果   04

27
  65    
第3组排序结果     49

49

97


(6)OK,本趟希尔排序的结果为:{13, 04, 49, 38, 27, 49, 55, 65, 97, 76};

(7)最后取增量为1,进行最后一次直接插入排序,因为此时序列已经“基本有序”,只需作少量的比较和移动即可完成排序。



5、算法示例(传入参数为顺序表的指针,建议用引用),另注R[0]不是哨兵:

(1)首先是一趟希尔排序的实现函数(三种写法):

//对顺序表L进行一趟希尔排序, 增量为dk
void  ShellInsert1(SqList* L,  int  dk)
{
     int  i;
     int  j;
     
     // 对顺序表L作一趟希尔插入排序
     for  (i = dk + 1; i <= L->Length; ++i)   
     {
         if  (L->R[i].key < L->R[i - dk].key)  
         {  
             L->R[0] = L->R[i];  // 暂存数据
             for  (j = i - dk; j > 0 && L->R[0].key < L->R[j].key; j -= dk)
             {
                 L->R[j + dk] = L->R[j];   // 记录后移,查找插入位置
             }
             L->R[j + dk] = L->R[0];       // 插入
        
     }
}

//对顺序表L进行一趟希尔排序, 增量为dk
void  ShellInsert2(SqList* L,  int  dk)
{
     int  j;
 
     // 从每一组的第二个开始,实现组内插入排序
     for ( int  i = dk + 1; i <= L->Length; i++)
     {
         L->R[0] = L->R[i];  //将待插入的i号元素暂存入0号元素以防后移有序元素时覆盖丢失
         j = i - dk;
 
         while ((j > 0) && (L->R[0].key < L->R[j].key))
         {
             L->R[j + dk] = L->R[j];
             j = j - dk;
         }
         L->R[j + dk] = L->R[0];
     }
}

//对顺序表L进行一趟希尔排序, 增量为dk
void  ShellInsert3(SqList* L,  int  dk)
{
     int  i;
     int  j;
 
     // 对顺序表L作一趟希尔插入排序
     for  (i = dk + 1; i <= L->Length; ++i)   
     {
         if  (L->R[i].key < L->R[i - dk].key)  
         {  
             L->R[0] = L->R[i];  // 暂存数据
             j = i - dk;
 
             do 
             {
                 L->R[j + dk] = L->R[j];
                 j = j - dk;
 
             while  ((j > 0) && (L->R[0].key < L->R[j].key));
 
             L->R[j + dk] = L->R[0];       // 插入
        
     }
}

(2)按照增量调用上述函数即可实现希尔排序:

// 根据增量数组dk进行希尔排序,按增量序列dk[0 … t-1],对顺序表L进行希尔排序
void  ShellSort(SqList* L,  int  dk[],  int  t)
{
     for ( int  i = 0; i < t; i++)
     {
         // 任选一个插入函数,完成一趟排序
         ShellInsert1(L, dk[i]);
         //ShellInsert2(L, dk[i]);
         //ShellInsert3(L, dk[i]);
     }
}


6、附上测试用例的调用代码:

int  _tmain( int  argc, _TCHAR* argv[])
{
     SqList L;
     
     // 初始化数据
     L.Length = 10;
     int  nNum[11] = {0, 49, 38, 65, 97, 76, 13, 27, 49, 55, 04};
     for  ( int  i = 0; i <= L.Length; i++)
     {
         L.R[i].key = nNum[i];
     }
 
     // 按当前顺序显示
     Fun::PrintNowOrder(&L);
 
     // 增量数组
     int  dk[10] = {5, 3, 1};
     int  t = 3;
     
     // 进行希尔排序
     ShellSort(&L, dk, t);
 
     // 排序结果
     Fun::PrintNowOrder(&L);
 
     getchar ();
     return  0;
}

7、运行结果:

排序算法(八)希尔排序(缩小增量排序)_第1张图片


8、由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。


9、希尔排序重点在于选择一个较好的增量序列,一般的初次取序列的一半为增量,以后每次减半,直到增量为1。但是,这样的增量序列不是最佳的,这是一个课题。



你可能感兴趣的:(排序算法)