排序 - 选择排序 / Selection Sort
选择排序是最自然地排序思路。
不就是排序嘛,每次挑出最小的那个,排到前面去。就是这么简单。
Selection Sort 循环
proc selection-sort {list n} {
for {set i 0} {$i<$n} {incr i} {
;# 选出最小的,交换到第一个位置
select-min &list $i $n
}
}
选出最小的
proc select-min {list start stop} {
upvar ${&list} list
set min_index $start
set min_value [lindex $list $start]
list-foreach {value index} $list $start $stop {
if {$value < $min_value} {
set min_value $value
set min_index $index
}
}
list-swap &list $start $min_index
}
Selection Sort 递归
递归描述,更有利于理解算法的思路。
proc selection-sort-recur {list start stop} {
if {$start>=$stop} {
return $list
}
#% 基本操作
set list [select-min $list $start $stop]
selection-sort-recur $list [incr start] $stop
}
在 Tcl 语言里,可以借助 tailcall
命令避免栈溢出(Stack Overflow)。
Less Writing - 更少的写操作
选择排序的一个好处是需要的写操作比较少。
相对于 Insertion Sort 或者 Bubble Sort 来说,Selection Sort 每个会回选出最小值之后,只需要一次交换操作 —— 两次写入操作。
很多时候,计算机的写操作可能会需要更多的时间。
Cycle Sort 是理论上写入操作最少的排序算法。
Stable Selection Sort
对于排序算法来说,所谓的 stable
指的是排序后相同的值的相对顺序不便。
对于上面的选择排序实现来说,swap 的步骤有可能导致相对顺序改变。
因此,如果需要一个 Stable Selection Sort,需要用 list-shift
来进行位置调整。
同时选出最小的和最大的
- cocktail sort - double selection sort
proc cocktail-sort {list n} {
for {set i 0} {$i<$n} {incr i ; incr n -1} {
;# 选出最小的,交换到第一个位置
select-min-max &list $i $n
}
}
bingo sort - 利用重复的最小值
如果序列中有很多重复项,那么也许可以在查找最小值的过程中利用这个信息。
问题的窍门在于,在当前查找最小值的过程中,当前回合的最小值还不确定,但上一回合的最小值是确定的。因此如果遇到了和上一回合的最小值一样的值,可以直接判定它就是最小值。
proc select-min-bingo {list start stop} {
upvar ${&list} list
set min_index $start
set min_value [lindex $list $start]
set min_prev [lindex $list $start-1]
list-foreach {value index} $list $start $stop {
if {$value == $min_prev} {
#% 发现最小值
list-swap &list $start $index
incr start
} elseif {$value < $min_value} {
set min_value $value
set min_index $index
}
}
list-swap &list $start $min_index
}
上面的代码不是局部最优的。可以通过改变比较操作的顺序进一步局部优化。
Heap Sort 堆排序
选择排序的关键是选择最小或最大值。
如果元素序列是按照特定的方式排列的,是可以加速选择过程的。
堆排序就是这样一种排序算法。可以把时间复杂度从 n^2
变为 n*log(n)