希尔排序法的代码实现:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
times := int64(time.Now().Nanosecond())
rand.Seed(times)
l := 100000
list := make([]int, l)
for i := 0; i < l; i++ {
list[i] = rand.Intn(l)
}
start := time.Now()
ShellSort(list)
end := time.Now()
dur := end.Sub(start)
fmt.Println(dur) // 显示消耗的时间
}
// 希尔排序函数
func ShellSort(list []int) {
iLen := len(list)
for i := 1; i < iLen; i++ {
d := (iLen - 1) / i
for j := 0; j+d < iLen; j++ {
if list[j] > list[j+d] {
list[j], list[j+d] = list[j+d], list[j]
}
}
}
}
Output:
command-line-arguments
24.0993784s
经过多次测试,10万个随机数排序所需要的时间基本都维持在25秒左右(原谅我的笔记本比较旧,跟随我多年了)。
如果在 d 每次赋值后,把 d 的值打印出来,如下面这样:
d := (iLen - 1) / i
fmt.Printf("%d, ", d)
可以发现 d 从某个值开始会出现重复,而且每个值重复的次数会越来越多。这就会造成非常多的重复操作,而这些操作还套着一重循环,这浪费了大量的执行时间。
发现问题,就可以解决问题。解决的办法就是在 d 出现重复时记录 d 的值,中止当前循环,进入到另一个从 d-1 到 1 的循环,来完成后面的排序,这样就可以有效的避免因 d 值重复在产生的大量重复操作。具体代码如下:
func ShellSort(list []int) {
iLen := len(list)
iMark := 0
for i := 1; i < iLen; i++ {
d := (iLen - 1) / i
if d == iMark { // 当 d 开始出现重复,则后面的都会重复而且重复次数越来越多,应该跳出该循环由后面的代码完成排序
break
}
for j := 0; j+d < iLen; j++ {
if list[j] > list[j+d] {
list[j], list[j+d] = list[j+d], list[j]
countSh++
}
}
iMark = d
}
// 此处代替重复循环,可减少大量无用的循环判断
for d := iMark - 1; d > 0; d-- {
for j := 0; j+d < iLen; j++ {
if list[j] > list[j+d] {
list[j], list[j+d] = list[j+d], list[j]
countSh++
}
}
}
}
测试结果如下:
Output:
command-line-arguments
349.0199ms
经过多次测试显示,同样10万个随机数进行排序,运行时间都维持在350毫秒到400毫秒之间,优化的效果还是非常明显的。
然后,还有一种写法,效率最好,代码如下:
func ShellSort2(list []int) {
iLen := len(list)
for d := iLen / 2; d > 0; d /= 2 {
for i := d; i < iLen; i++ {
for j := i; j-d >= 0 && list[j-d] > list[j]; j -= d {
list[j-d], list[j] = list[j], list[j-d]
}
}
}
}
Output:
command-line-arguments
43.0025ms
同样10万个随机数排序,只需要45毫秒左右。