【算法】超详细选择排序、二元选择排序实现过程、时间、空间复杂度计算

选择排序是先从元素列中找到最大/最小的元素(升序时找到最小值,降序时找到最大值),将它放在第一位,再从剩余元素中找到最大/最小,将它排到第二位,直到所有元素都被排序。

本文以最详尽的方式讲述选择排序的实现过程,及其代码实现、时间、空间复杂度计算、优化方法(二元选择排序)。

实现过程:

以升序为例,选择排序每次假定需要排序的第一位数字即为最小值,向后依次对比,出现比它更小的值,就交换位置,直到这一列全部对比完,确定最小值并将它放到第一位,再进行第二次遍历,找到第二小的值。

以数组$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,14,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}-n)/2,在计算时间复杂度时常数系数、低阶项可以忽略,所以其时间复杂度为O(n^{2}),与冒泡排序一样,尽管与冒泡排序相比,选择排序可以减少一些位置交换,但是并没减少判断次数。

空间复杂度计算:

本例使用了$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(n^{2})级的,空间复杂度还是常量级的,从量级上看不出优势,但确实减少了嵌套循环次数。

个人表达水平的限制,没办法把二元选择排序讲的更清晰,以后能讲的更好的话我会回来更新这一篇。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值