Shell-Sort 增量排序算法 总结

1.Shell - Sort

希尔排序(Shell Sort)是插入 排序 的一种。是针对直接插入排序 算法 的改进。该方法又称缩小 增量 排序,因DL.Shell于1959年 提出 而得名。
我们都知道直接插入排序算法是相对来说比较低效的算法,但是正是插入排序算法的特性决定了我们在数据量小的时候,数据基本有序的时候的排序效果往往比一些高级排序算法构架行之有效,更加快速
在我们开始了解Shell-Sort算法之前,我们先来复习一下插入排序算法
在这里我们先给出插入排序的伪代码:
data - the array wait to sort
n - the count of the elements of the array
//本伪代码针对升序排列
for i=2 to (n-1)
    temp = data[i]
    j=i-1
    while j>=0 and temp<data[j]   //这里不加等号保证排序的稳定性
        data[j+1]=data[j]
        j--
    data[j+1]=temp

从上面我们可以看出,直接插入排序算法的在我们的数组基本有序的时候复杂度非常的高效,但是一旦出现了杂糅的情况我们的耗时就非常的大,基本在O(n^2)的时间复杂度水平
我们注意到,我们的直接插入排序算法的效率低下的原因在于我们每次找到我们的带插入的位置后,我们都需要遍历这个位置的路径,大致我们的交换次数变得非常的冗长
所以,聪明的Shell就想到了一种非稳定的基于直接插入排序算法的高效的排序算法,也就是大名鼎鼎的  Shell - Sort

2.Shell - Sort 的原理

我们先引出我们的优化的机理,我们对于直接插入排序的优化基于一点就是 - 直接插入排序在基本有序的时候插入效率非常的高效,所以说我们的优化的核心就浮出水面了
我们不断的令待排数组的的有序度上升,让数组的有序性逐步增加,每一次我们优化之后,数组都变得更加相对的有序,这时候下一次的插入排序的效率就会变得更加的高效
在这里,机智的科学家发明了Shell -Sort,也就是增量排序的想法来处理这个问题
我们每次选择一个增量,保证一个增量范围内的数组都是有序的,我们不断缩小我们的增量直到1,这是后我们最后增量位1的过程就相当于是一个直接插入排序,但是之前的操作已经保证我们的数组已经非常的有序,我们的最后的直接插入排序的过程的时间复杂度也变得非常的高效

每一个Shell - Sort需要几趟,每一趟都相当于一次断层的直接插入排序,如图所示

3.Shell - Path的选择

现在的事实已经证明了,希尔排序的核心在于我们的增量的选取,我们的增量选取要求最后都是要归终与一个为1的增量上
但是不同的选取导致我们的希尔排序的效率是不一样的
实践证明了有很多非常优秀的步长选择方案是的我们的希尔排序在中小规模上的排序时间效率甚至超过了快速排序

步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。

算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。
Donald Shell 最初建议步长选择为N/2并且对步长取半直到步长达到1。虽然这样取可以比O(N2)类的算法(插入排序)更好,但这样仍然有减少平均时间和最差时间的余地。可能希尔排序最重要的地方在于当用较小步长排序后,以前用的较大步长仍然是有序的。比如,如果一个数列以步长5进行了排序然后再以步长3进行排序,那么该数列不仅是以步长3有序,而且是以步长5有序。如果不是这样,那么算法在迭代过程中会打乱以前的顺序,那就

不会以如此短的时间完成排序了。

步长序列

最坏情况下复杂度

(3**(t-k)-1)/2

待查

已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),该序列的项来自

9*4^i-9*2^i+1 或者是 4^i-3*2^i+1这两个算式。

这项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。

希尔排序的伪代码如下:

data - array wait to sort
n - the count of the array
dlta - the array save the dlta_path
for path=0 to length_dlta
    for i=path to n   //一趟分段直接插入排序
        temp = data[i]
        j=i-path
        while j>=0 and temp<data[j]
            data[j+path]=data[j]
            j-=path
        data[j+path]=temp

3.Code of Python

from random import*
from time import*
from math import*
def shell_sort_0(data,n):
    time=clock()
    path=int(n/2);
    while path:
        i=path
        while i<n:
            temp=data[i]
            j=i-path
            while j>=0 and temp<data[j]:
                data[j+path]=data[j]
                j-=path
            data[j+path]=temp
            i+=1
        path=int(path/2)
    return clock()-time

def init_data(data,n):
    for i in range(n):
        data.append(randint(1,100000))
        
def make_dlta(dlta,n):
    t=(int)(log(2*n+1,3))
    for i in range(t):
        dlta.append((int)(0.5*(3**(t-i)-1)))

def shell_sort_1(dlta,data,n):
    time=clock()
    for path in dlta:
        i=path
        while i<n:
            temp=data[i]
            j=i-path
            while j>=0 and temp<data[j]:
                data[j+path]=data[j]
                j-=path
            data[j+path]=temp
            i+=1
    return clock()-time

data=[]
n=eval(input("请输入数据量:"))
init_data(data,n)
p=[]
dlta=[]

p=[i for i in data]
print("-----------")
print("time:%lf"%(shell_sort_0(p,n)))

p=[i for i in data]
make_dlta(dlta,n)
print("-----------")
print("time:%lf"%(shell_sort_1(dlta,p,n)))

time=clock()
p.sort()
print("time:%lf"%(clock()-time))
通过实验数据发现,我们的表格的第三种希尔排序的效率比我们的希尔最早提出的二分的鞥两还有更优
实际上,我们的希尔排序的步长选择的最优解实际上现在还是一个数学难题,我们目前只是找到了最好的解,但并不是说我们找到了最优的步长选择方案

实际上,因为希尔排序的常数因子的问题,在中小规模的时候,我们的希尔排序甚至比我们的快速排序还要更加的优秀,但是一旦数据量增大,我们也无能为力,只要采用O(n*logn)的效率的算法来进行优化

专家们建议:任何一个容许不稳定的排序任务我们都可以通过先用希尔排序的策略,一旦我们发现该问题还可以再度优化,我们再切换成更加高效的排序算法


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值