选择排序是先从元素列中找到最大/最小的元素(升序时找到最小值,降序时找到最大值),将它放在第一位,再从剩余元素中找到最大/最小,将它排到第二位,直到所有元素都被排序。
本文以最详尽的方式讲述选择排序的实现过程,及其代码实现、时间、空间复杂度计算、优化方法(二元选择排序)。
实现过程:
以升序为例,选择排序每次假定需要排序的第一位数字即为最小值,向后依次对比,出现比它更小的值,就交换位置,直到这一列全部对比完,确定最小值并将它放到第一位,再进行第二次遍历,找到第二小的值。
以数组$array=[8,4,1,5,9,3,0]为例
第一次遍历(下划线表示每次遍历需要依次对比的元素值):
8,4,1,5,9,3,0 //目前最小值是4
8,4,1,5,9,3,0 //目前最小值是1
8,4,1,5,9,3,0 //目前最小值是1
8,4,1,5,9,3,0 //目前最小值是1
8,4,1,5,9,3,0 //目前最小值是1
8,4,1,5,9,3,0 //遍历完成,目前最小值是0
第一次遍历结束,对比了6次,找到了最小的元素0,将其与目前$array[0]的元素8位置对换,新序列为:0,4,1,5,9,3,8。注意此过程确定不会影响到其他元素位置。
第二次遍历:
0,4,1,5,9,3,8 //目前最小值是1
0,4,1,5,9,3,8 //目前最小值是1
0,4,1,5,9,3,8 //目前最小值是1
0,4,1,5,9,3,8 //目前最小值是1
0,4,1,5,9,3,8 //遍历完成,目前最小值是1
第二次遍历结束,对比了5次,找到了第二小的元素1,将其与第一次遍历结束后新的$array[1]元素对换,新序列为:0,1,4,5,9,3,8,此过程依然不会影响其他元素位置。
第三次遍历:
0,1,4,5,9,3,8 //目前最小值是4
0,1,4,5,9,3,8 //目前最小值是4
0,1,4,5,9,3,8 //目前最小值是3
0,1,4,5,9,3,8 //遍历完成,目前最小值是3
第三次遍历结束,对比了4次,找到了第三小的元素3,将其与第二次遍历后新的$array[2]元素对换,新序列为:0,1,3,5,9,4,8,此过程依然不会影响其他元素位置。
第四次遍历:
0,1,3,5,9,4,8 //目前最小值是5
0,1,3,5,9,4,8 //目前最小值是4
0,1,3,5,9,4,8 //遍历完成,目前最小值是4
第四次遍历结束,对比了3次,找到了第四小的元素4,将其与第三次遍历后新的$array[3]元素对换,新序列为:0,1,3,4,9,5,8,此过程依然不会影响其他元素位置。
第五次遍历:
0,1,3,4,9,5,8 //目前最小值是5
0,1,3,4,9,5,8 //遍历完成,目前最小值是5
第五次遍历结束,对比了2次,找到了第五小的元素5,将其与第四次遍历后新的$array[4]元素对换,新序列为:0,1,3,4,5,9,8,此过程依然不会影响其他元素位置。
第六次遍历:
0,1,3,4,5,9,8 //遍历完成,目前最小值是8
第六次遍历结束,对比了1次,找到了第六小的元素8,将其与第五次遍历后新的$array[5]元素对换,新序列为:0,1,3,4,5,8,9,此过程依然不会影响其他元素位置。
因为只剩下一个元素9,不必再遍历即可认定其为最大值,不必再判断、交换。
对于一个长度为7的数组,我们遍历了7-1=6次,每次对比元素数量为7-1=6,7-2=5,7-3=4,7-4=3,7-5=2,7-6=1次
PHP代码实现:
$array=[8,4,1,5,9,3,0];
$count = count($array);
echo '<pre>';
for ($i=1; $i < $count; $i++) {//第i次遍历
$minIndex = $i-1;//当前最小值的下标
for ($j=$i; $j < $count; $j++) {//从本次需要排序的下标向后对比
if($array[$j]<$array[$minIndex]){
$minIndex = $j; //找到最小元素值的下标记做$minIndex
}
}
$temp = $array[$i-1];//交换本次找到的最小值的和本次遍历首位的元素位置
$array[$i-1] = $array[$minIndex];
$array[$minIndex] = $temp;
/*需要打印输出验证可以加上此段
echo $i.'----'.$j."\n";
print_r($array);die;
*/
}
print_r($array);
GO代码实现:
package main
import "fmt"
func main() {
arr := []int{1,23,35,22,7,9,0}
arr = xuan(arr)
fmt.Println(arr)
}
func xuan(arr []int) []int{
for i :=0;i< len(arr);i++{
minkey := i
for j :=i+1;j< len(arr);j++{
if arr[j] < arr[minkey]{
minkey = j
}
}
tmp := arr[minkey]
arr[minkey] = arr[i]
arr[i] = tmp
}
return arr
}
时间复杂度计算:
数组长度为n,需要遍历次数为(n-1)+(n-2)+.....+1=(-n)/2,在计算时间复杂度时常数系数、低阶项可以忽略,所以其时间复杂度为O(),与冒泡排序一样,尽管与冒泡排序相比,选择排序可以减少一些位置交换,但是并没减少判断次数。
空间复杂度计算:
本例使用了$count、$i、$j、$minIndex这些变量来辅助计算,在整个程序运行过程中的任何一个时刻,他们最多全部使用一次,且所使用的变量数量不随数组长度变大而增多,因此其空间复杂度是一个常量,记做O(1),这个也与冒泡排序一致。
优化方法(二元选择排序):
选择排序每次遍历确定一个元素,如果每次遍历可以确定两个元素(当前遍历出的最大值最小值),循环次数就能大大减少,这就是二元选择排序。
示例数值$array=[17,1,2,4,5,9,3,0,1,12],依然升序排列,它的实现过程如下:(交换元素用下划线表示,红色表示本次交换后目前已确定的位置)
17,1,2,4,5,9,3,0,1,12
交换最小值=》0,1,2,4,5,9,3,17,1,12 交换最大值=》0,1,2,4,5,9,3,12,1, 17
交换最小值=》0,1,2,4,5,9,3,12,1, 17 交换最大值=》0,1,2,4,5,9,3,1,12, 17
... ...
二元选择排序PHP代码实现:
因为二元选择相对不太好理解,所以写了升序、降序两种demo,升序:
$array=[17,1,2,4,5,9,3,0,1,12];
$count = count($array);
echo '<pre>';
for ($i=1; $i<= intval($count/2); $i++) {
$min_index = $max_index = $i-1;//每次循环重置当前最小值的下标
for ($j=$i; $j < $count-($i-1); $j++) {//从本次需要排序的下标向后对比。需要注意的是已经排序的前后两部分数据都不再参与对比
if($array[$j]<$array[$min_index]){
$min_index = $j;
}
elseif($array[$j]>$array[$max_index]){
$max_index = $j;
}
}
//更换最小值
$min_temp = $array[$min_index];
$array[$min_index] = $array[$i-1];
$array[$i-1] = $min_temp;
if($i-1 == $max_index){ //如果$i-1位置原本恰好放的是当前最大值,那么刚才的调换已经把原本最大值的位置和最小值交换了,在之后交换最大值时需要用它新的位置来进行。
$max_index = $min_index;
}
//更换最大值
$max_temp = $array[$max_index];
$array[$max_index] = $array[$count-$i];
$array[$count-$i] = $max_temp;
}
print_r($array);
降序:
$array=[-5,17,1,2,4,5,9,3,0,1,12,-10];
$count = count($array);
echo '<pre>';
for($i=1;$i<= intval($count/2);$i++){
$max_index = $min_index = $i-1;
for($j=$i;$j<$count-($i-1);$j++){
if($array[$j] > $array[$max_index]){
$max_index = $j;
}elseif($array[$j] < $array[$min_index]){
$min_index = $j;
}
}
$max_temp = $array[$max_index];
$array[$max_index] = $array[$i-1];
$array[$i-1] = $max_temp;
if($i-1 == $min_index){ //在降序时也需要考虑先调整的对后调整的影响,当刚被调换的元素恰好是当前最小值时,要把最小值索引更新
$min_index = $max_index;
}
$min_temp = $array[$min_index];
$array[$min_index] = $array[$count - $i];
$array[$count - $i] = $min_temp;
}
print_r($array);
优化后的代码时间复杂度虽然比之前减少,但仍然是O()级的,空间复杂度还是常量级的,从量级上看不出优势,但确实减少了嵌套循环次数。
个人表达水平的限制,没办法把二元选择排序讲的更清晰,以后能讲的更好的话我会回来更新这一篇。