详解插入排序和优化之后的希尔(shell)排序

版权声明:转载需注明链接 https://blog.csdn.net/q745401990/article/details/14484575

插入排序的过程:从未排序的队列中取最前端的一个值,插入到有序队列中。

像是玩朴克一样,我们将牌分作两堆,每次从后面一堆的牌抽出最前端的牌,然后插入前面一堆牌的适当位置,例如:

排序前:92 77 67 8 6 84 55 85 43 67

[77 92] 67 8 6 84 55 85 43 67 77插入92前 

[67 77 92] 8 6 84 55 85 43 67 67插入77前 

[8 67 77 92] 6 84 55 85 43 67 8插入67前 

[6 8 67 77 92] 84 55 85 43 67 6插入8前 

[6 8 67 77 84 92] 55 85 43 67 84插入92前 

[6 8 55 67 77 84 92] 85 43 67 55插入67前 

[6 8 55 67 77 84 85 92] 43 67 ...... 

[6 8 43 55 67 77 84 85 92] 67 ...... 

[6 8 43 55 67 67 77 84 85 92] ...... 


void InsertSort(int number[])
{
     int i, j, tmp;
     for(i = 2; i <=  MAX; i++)
     {
           tmp = number[i];
           j = i - 1;
           while(tmp < number[j])
           {
                number[j+1] = number[j];
                j--;
                if(j == 0) break;	          
           }
           number[j+1] = tmp;      
     }          
     printf("Sorted array is : ");
     for(i = 1; i <= MAX; i++)
           printf("%d ",number[i]);
     printf("\n");
}

数组下标从1 开始, MAX 表示在数组中的个数 


很明显插入排序的时间复杂度T(n) = O(n*n);从插入的排序过程可以看出,主要的时间消耗在寻找有序队列中的位置上,那么排序要加快的基本原则之一,是让后一次的排序进行时,尽量利用前一次排序后的结果,以加快排序的速度,Shell排序法即是基于此一概念来改良插入排序法。

Shell排序法最初是D.L Shell1959所提出,假设要排序的元素有n个,则每次进行插入排序时并不是所有的元素同时进行时,而是取一段间隔。

Shell首先将间隔设定为n/2,然后跳跃进行插入排序,再来将间隔n/4,跳跃进行排序动作,再来间隔设定为n/8n/16,直到间隔为1之后的最 后一次排序终止,由于上一次的排序动作都会将固定间隔内的元素排序好,所以当间隔越来越小时,某些元素位于正确位置的机率越高,因此最后几次的排序动作将 可以大幅减低。

举个例子来说,假设有一未排序的数字如右:89 12 65 97 61 81 27 2 61 98

数字的总数共有10个,所以第一次我们将间隔设定为10 / 2 = 5,此时我们对间隔为5的数字进行排序,如下所示:




画线连结的部份表示 要一起进行排序的部份,再来将间隔设定为5 / 2的商,也就是2,则第二次的插入排序对象如下所示:


再来间隔设定为2 / 2 = 1,此时就是单纯的插入排序了,由于大部份的元素都已大致排序过了,所以最后一次的插入排序几乎没作什么排序动作了: 



将间隔设定为n / 2D.L Shell最初所提出,在教科书中使用这个间隔比较好说明,然而Shell排序法的关键在于间隔的选定,例如Sedgewick证明选用以下的间隔可以加 快Shell排序法的速度:


其中4*(2j)2 + 3*(2j) + 1不可超过元素总数n值,使用上式找出j后代入4*(2j)2 + 3*(2j) + 1求得第一个间隔,然后将2j除以2代入求得第二个间隔,再来依此类推。 

后来还有人证明有其它的间隔选定法可以将Shell排序法的速度再加快;另外Shell排序法的概念也可以用来改良气泡排序法。


下面介绍下代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX 10
#define SWAP(x,y) {int t; t = x; x = y; y = t;}
/*
void InsertSort(int number[])
{
     int i, j, tmp;
     for(i = 2; i <=  MAX; i++)
     {
           tmp = number[i];
           j = i - 1;
           while(tmp < number[j])
           {
                number[j+1] = number[j];
                j--;
                if(j == 0) break;	          
           }
           number[j+1] = tmp;      
     }          
     printf("Sorted array is : ");
     for(i = 1; i <= MAX; i++)
           printf("%d ",number[i]);
     printf("\n");
}*/
 
void UltimateInsertSort(int number[])
{
     int gap = MAX/2, i,j, tmp;
     while(gap > 0)
     {
         for(i = gap; i <= MAX; i++)
         {
             tmp = number[i];
             j = i - gap;
             while(j >= 1 && tmp < number[j])
             {
                 number[i] = number[j];
                 number[j] = tmp;
                 j -= i;
             }      
         } 
         gap /= 2;         
     }
     
     printf("Sorted array is : ");
     for(i = 1; i <= MAX; i++)
           printf("%d ",number[i]);
     printf("\n");
} 

 
int main()
{
    int number[MAX+1];
    int i;
    
    srand(time(NULL));
    
    printf("Original array is : ");
    for(i = 1; i <= MAX; i++){
          number[i] = rand() % 100;
          printf("%d ", number[i]);      
    }
    printf("\n");
 //   InsertSort(number);
    UltimateInsertSort(number);
    system("pause");
    return 0;    
}


不需要大量的辅助空间,和归并排序一样容易实现。希尔排序是基于插入排序的一种算法, 在此算法基础之上增加了一个新的特性,提高了效率。希尔排序的T(n)与增量序列的选取有关,例如希尔增量时间复杂度为O(n²),而Hibbard增量的希尔排序的时间复杂度为O(N(3/2)),但是现今仍然没有人能找出希尔排序的精确下界。希尔排序没有快排快 O(N*(logN)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。但是比O(N^2)复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。 此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。所以,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法. 本质上讲,希尔的一种改进,减少了其复制的次数,速度要快很多。 原因是,当N值很大时数据值每一趟排序需要的个数很少,但数据项的距离很长。当N值减小时每一趟需要和动的数据增多,此时已经接近于它们排序后的最终位置。 正是这两种情况的结合才使希尔排序效率比插入排序高很多。



阅读更多
换一批

没有更多推荐了,返回首页