希爾排序
其他排序方法:選擇排序、冒泡排序、歸並排序、快速排序、插入排序、希爾排序、堆排序
思想
希爾排序大概就是,選一組遞減的整數作為增量序列。最小的增量必須為1:\(D_M>D_{M-1}>...>D_1=1\)
- 先用第一個增量把數組分為若干個子數組,每個子數組中的元素下標距離等於增量;
- 然后對每個子數組進行簡單插入排序
- 再使用第二個增量,繼續同樣的操作,直到增量序列里的增量都使用過一次。
(增量為1時,其實就是對整個數組進行簡單插入排序)
圖解
看圖更容易理解吧:
(借用一下慕課的浙大數據結構課件。因為課件原本是ppt,而我只有pdf,所以顏色沒有上齊,請將就將就emmm)
性能
希爾排序快不快主要取決於我們怎么取增量序列,原始希爾排序的取法就是:\(D_M=\lfloor N/2 \rfloor, D_k=\lfloor D_{k+1}/2 \rfloor\)
此增量序列也成為Shell增量序列
原始希爾排序最壞的時間復雜度為\(O(n^2)\)
代碼
# 原始希爾排序
# 增量序列為D(M)=N/2, D(k)=D(k+1)/2 向下取整
def shellSort(arr):
size = len(arr)
# 正整數右移一位相當於除以2且向下取整
step = size >> 1
while step > 0:
for i in range(step, size):
j = i
tmp = arr[j]
while j >= step:
if tmp < arr[j - step]:
arr[j] = arr[j - step]
j -= step
else:
break
arr[j] = tmp
step = step >> 1
優化
希爾排序的優化主要是針對增量序列的優化。
增量序列如果取得不好,效率比直接插入排序還要低,下面舉個例子(直接借用課件了):
在這個例子里,前幾個增量沒有起到任何作用(只起到了拖延時間的作用哈哈)。
所以有人就發現了,如果增量之間不互質的話,那有些情況就不管用了。
於是有些大佬們就整出了下面這些增量序列:Hibbard增量序列、Knuth增量序列、Sedgewick增量序列等等
下面主要介紹Hibbard增量序列和Sedgewick增量序列
Hibbard增量序列
Hibbard增量序列的取法為\(D_k=2^k-1\):{1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191...}
最壞時間復雜度為\(O(N^{3/2})\);平均時間復雜度約為\(O(N^{5/4})\)
先來個Hibbard增量序列的獲取代碼:
# Hibbard增量序列
# D(i)=2^i−1,i>0
def getHibbardStepArr(n):
i = 1
arr = []
while True:
tmp = (1 << i) - 1
if tmp <= n:
arr.append(tmp)
else:
break
i += 1
return arr
排序代碼稍微修改一下就行:
# 希爾排序(Hibbard增量序列)
def shellSort(arr):
size = len(arr)
# 獲取Hibbard增量序列
stepArr = getHibbardStepArr(size)
# 因為要倒着使用序列里的增量,所以這里用了reversed
for step in reversed(stepArr):
for i in range(step, size):
j = i
tmp = arr[j]
while j >= step:
if tmp < arr[j - step]:
arr[j] = arr[j - step]
j -= step
else:
break
arr[j] = tmp
至於為什么要用python內置函數reversed()
,而不用其它方法,是因為reversed()
返回的是迭代器,占用內存少,效率比較高。
如果先使用stepArr.reverse()
,再用range(len(arr))
的話,效率會比較低;
而且實測reversed
也比range(len(arr) - 1, -1, -1)
效率高,故使用reversed()
;
還有就是先stepArr.sort(reverse=True)
,再用range(len(arr))
,同樣效率低。
這幾種方法比較的測試代碼在這里,有興趣的朋友可以看看:Python列表倒序輸出及其效率
Sedgewick增量序列
Sedgewick增量序列的取法為\(D=9*4^i-9*2^i+1\)或\(4^i-3*2^i+1\):{1, 5, 19, 41, 109, 209, 505, 929, 2161...}
最壞時間復雜度為\(O(N^{4/3})\);平均時間復雜度約為\(O(N^{7/6})\)
Sedgewick增量序列的獲取代碼:
# Sedgewick增量序列
# D=9*4^i-9*2^i+1 或 4^(i+2)-3*2^(i+2)+1 , i>=0
# 稍微變一下形:D=9*(2^(2i)-2^i)+1 或 2^(2i+4)-3*2^(i+2)+1 , i>=0
def getSedgewickStepArr(n):
i = 0
arr = []
while True:
tmp = 9 * ((1 << 2 * i) - (1 << i)) + 1
if tmp <= n:
arr.append(tmp)
tmp = (1 << 2 * i + 4) - 3 * (1 << i + 2) + 1
if tmp <= n:
arr.append(tmp)
else:
break
i += 1
return arr
排序代碼稍微修改一下就行:
# 希爾排序(Sedgewick增量序列)
def shellSort(arr):
size = len(arr)
# 獲取Sedgewick增量序列
stepArr = getSedgewickStepArr(size)
for step in reversed(stepArr):
for i in range(step, size):
j = i
tmp = arr[j]
while j >= step:
if tmp < arr[j - step]:
arr[j] = arr[j - step]
j -= step
else:
break
arr[j] = tmp