七大排序算法(3)------希尔排序

        在本文中使用到的升序,降序,交换函数的代码以及插入排序的思想见:这篇博客

        在上述博客中的插入排序中有介绍了插入排序的两个特点:

(1)当待排序序列的有序性比较高时,排序的效率比较高;

(2)当待排序序列的元素个数较少时,排序的效率比较高。

        本文中介绍的希尔排序就是利用插入排序的两个特点来实现的。

希尔排序

        例如,待排序序列为:10 30 20 60 50 30 20 70 90

(1)首先取步长gap为3,进行如下分组,然后在组内先进行插入排序:

第一组:10 60 20       排序后:10 20 60

第二组:30 50 70       排序后:30 50 70

第三组:20 30 90       排序后:20 30 90

        将上述排序后的分组再按原下标进行合并:10 30 20 20 50 30 60 70 90

(2)再取步长gap为2,进行如下分组,然后在组内进行插入排序:

第一组:10 20 50 60 90    排序后:10 20 50 60 90

第二组:30 20 30 70         排序后:20 30 30 70

        将上述排序后的分组再按原下标进行合并:10 20 20 30 50 30 60 70 90

(3)最后取步长gap为1,直接进行插入排序:10 20 20 30 30 50 60 70 90

        在上述中首先分为多组,组内进行插入排序,可以使待排序的元素个数减少,然后利用插入排序既可以提高效率。然后多个组内排序之后,整个待排序序列的有序性比较高,,因此之后再对步长小的序列进行插入排序时,同时可以调高效率。

        上述过程就是希尔排序的实现思路。不同的是,希尔排序对于步长的选取是一个希尔序列:n/2  n/4  n/8   ...   1。其中n为待排序序列的元素个数。

        根据上述例题,希尔排序的代码实现思路为:取一个步长,根据该步长进行分组,然后在组内进行插入排序。

        步长的选取是一个变化的过程,即有一组步长。一个步长对应一组分组,一个分组对应一个插入排序。

(1)首先通过一个循环来表示一组变化的步长,初始步长gap为size/2,之后每次在原基础上除2,即gap = gap/2;

(2)根据一个步长确定一组分组,各分组下标为:

        第一个分组为:0      0+ gap   0+ 2gap,....

        第二个分组为:1      1+gap   1+2gap,...

        第三个分组为:2      2+gap   2+2gap,...

        在一个分组内进行插入排序,在插入排序中有一边界值bound,所以对各分组进行插入排序时,各分组内边界值初始分别为:gap  gap+1  gap+2  ...  所以可以令bound初始为gap,之后逐次加1,表示各分组内的边界初始值。

        在进行插入排序时,因为同一分组内的各元素下标不连续,但不同分组的下标是连续的,所以:

(1)首先对第一个分组的第二个元素进行插入排序,

        对第二个分组的第二个元素进行插入排序,

        对第三个分组的第二个元素进行插入排序,...

(2)然后对第一个分组的第三个元素进行插入排序

         对第二个分组的第三个元素进行插入排序

        对第三个分组的第三个元素进行插入排序,...

(3)依次往后,...直到将所有元素排好序。

        在插入排序中,各分组的某一个元素进行排序时,与该元素同一分组的上一个元素下标为:bound-gap。所以在取定某一个边界值bound对其进行插入时,此时,cur初始设置为bound,cur-1即为cur-gap。

1)每次将边界bound处的值bound_value与cur-gap处的值进行比较。

2)如果bound_value大于cur-gap处的值,则cur就是bound_value所放置的位置;

3)如果小于,则将cur-gap处的值移到cur处,然后cur = cur-gap,在进行1)~3)的操作。如果cur的值减至各分组的第一个元素的下标即cur小于gap时,则停止移动,此时cur就是bound_value所放置的位置。

        根据上述思路,实现代码如下:

//希尔排序
void ShellSort(int arr[],uint64_t size,Compare cmp)
{
    if(arr == NULL || size <= 1)
    {   
        return;
    }   

    int gap = size/2;

    //第一重循环,对待排序序列进行分组
    for(;gap >= 1;gap = gap/2)
    {   
        //第二重循环:计算边界值
        int bound = gap;
        //第一次对第一组的第一个元素进行排序
        //第二次对第二组的第一个元素进行排序
        //......
        //对第一组的第二个元素进行排序
        //对第二组的第二个元素进行排序
        //。。。
        for(;bound < size;bound++)
        {   
            //在组内定义cur进行插入排序                                                                         
            int cur = bound;
            int bound_value = arr[bound];//保存bound处的值
            //这里注意cur的判断条件:假设步长取值为3
            //则对应的三个分组第一个元素分别为:0,1,2
            //对任一分组,cur减至每个分组的第一个元素下标时,停止前移
            //因为分组的不同,对应的第一个元素下标也不同,但是任一分组第一个元素下标都是小于步长3的
            //所以对任一分组,可以将条件设置为当cur的值小于步长3时停止前移
            //也就是说cur大于等于步长3时仍可以前移,所以这里的条件为:cur>=gap
            for(;cur >= gap;cur = cur - gap)
            {
                if(cmp(arr[cur - gap],bound_value) == 1)
                {
                    arr[cur] = arr[cur - gap];
                }
                else
                {
                    break;
                }
            }
            arr[cur] = bound_value;                                                                             
        }
    }
}

        希尔排序的时间复杂度取决于步长序列。在本文中使用的是希尔序列,因而时间复杂度为:O(N^2)。空间复杂度为O(1)。稳定性为:不稳定

          如果选取一个最优的序列,可以是时间复杂度降低至:O(N^1.3)  















猜你喜欢

转载自blog.csdn.net/sandmm112/article/details/80522106