希爾排序
前言
插入排序在某些情況下,效率是很高的。
比如我們的記錄本身就是基本有序的,只需要少量的插入操作。
再比如,記錄少的時候,插入排序效率的優勢也是很明顯的。
可是現實中,哪有那麼理想的情況啊[>_<]
沒有環境,那我們就要自己創造啊!!!
基本有序:
所謂的基本有序就是小的關鍵字在前面,大的基本在後面,不大不小的基本在中間。
希爾排序的思想核心
關鍵詞: 步長
、分組
我們首先根據一定的步長
,將原有的記錄進行分組
。
在分組內,使用插入排序把分組變成有序。
然後調整增量
,繼續對分組做插入排序
處理。
直至最後步長變爲1
,此時整個記錄已經基本有序了,再進行少量的插入操作,就可以把整個記錄變成有序。
舉個例子:
代碼實現
func shellSortStep(arr []int, start int, gap int) []int {
// 數組長度
length := len(arr)
// 插入排序的變種
for i := start + gap; i < length; i += gap {
// 備份待插入的數據
backup := arr[i]
// 初始化待插入位置
j := i - gap
// 待插入數據,小於前面的數據
for j >= 0 && backup < arr[j] {
// 從前往後移動
arr[j+gap] = arr[j]
j -= gap
}
arr[j+gap] = backup
}
return arr
}
func ShellSort(arr []int) []int {
// 數組長度
length := len(arr)
// 數組爲空或者只有一個元素
if length <= 1 {
return arr
}
// 步長
gap := length / 2
for gap > 0 {
// 處理每個元素的步長
for i := 0; i < gap; i++ {
shellSortStep(arr, i, gap)
}
gap /= 2
}
return arr
}
題外話:增量怎麼確定?
有人說,按照Knuth序列,應該是這樣的:
// 如果h最終爲1
h = 1
// 則h的計算公式爲,同時h不應該大於1/3,否則就比整個數組長度還要大了
h = 3*h - 1
Kunth序列版代碼實現
func shellSortStep(arr []int, start int, gap int) []int {
// 數組長度
length := len(arr)
// 插入排序的變種
for i := start + gap; i < length; i += gap {
// 備份待插入的數據
backup := arr[i]
// 初始化待插入位置
j := i - gap
// 待插入數據,小於前面的數據
for j >= 0 && backup < arr[j] {
// 從前往後移動
arr[j+gap] = arr[j]
j -= gap
}
arr[j+gap] = backup
}
return arr
}
func ShellSort(arr []int) []int {
// 數組長度
length := len(arr)
// 數組爲空或者只有一個元素
if length <= 1 {
return arr
}
// 步長
gap := 1
for gap <= length/3 {
gap = gap*3 + 1
}
for gap > 0 {
// 處理每個元素的步長
for i := 0; i < gap; i++ {
shellSortStep(arr, i, gap)
}
gap = (gap - 1) / 3
}
return arr
}
時間複雜度
希爾排序的時間複雜度是:O(n^(1.3—2))
爲什麼是個區間呢?
因爲時間複雜度和增量的選擇有關,選擇不同的增量會有不同的效果。
但不論怎麼說,希爾排序的性能要好於直接排序的O(n^2)。
穩定性
由於希爾排序在排序的過程中,記錄時跳躍式的移動,所以希爾排序是一種不穩定排序算法。