计算与数据结构篇 - 排序(冒泡/插入/选择)

计算与数据结构篇 - 排序(冒泡/插入/选择)

排序是一个特别常见的问题,也是算法与数据结构中绕不开的话题,有次去小米,面试官问我这样一个场景,每周四10点是小米开放日,在一段时间内,会涌进2kw的数据流,怎么排序,当时会有订单状态?

当时我第一反应是冒泡、快排和二分,但好像都不能解决这个问题,结尾我会做出解答。

判断排序算法的执行效率因素

  • 排序算法的执行效率
    • 最好情况、最坏情况、平均情况时间复杂度
    • 时间复杂度的系数、常数 、低阶
    • 比较次数和交换(或移动)次数
  • 排序算法的内存消耗
    • 所谓原地排序算法,就是特指空间复杂度是 O(1) 的排序算法。
  • 排序算法的稳定性
    • 稳定性,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

稳定性是非常重要的一个指标,在实际开发中即满足交易时间,又要根据订单状态两个因素排序的时候,排序就会有很大的不同,比如我们有一组数据 2,1,3,4,8,3,按照大小排序之后就是 1,2,3,3,4,8,9。

冒泡、插入、选择

冒泡排序法(Bubble Sort)

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。

	// 定义一个随机的数组
	$a = array(1233489 );

	// 第一层可以理解为从数组中键为0开始循环到最后一个
	for ($i = 0; $i < count($a) ; $i++) {
	  // 第二层为从$i+1的地方循环到数组最后
	    for ($j = $i+1; $j < count($a); $j++) {
	     // 比较数组中两个相邻值的大小
	        if ($a[$i] > $a[$j]) {
	            $tem = $a[$i]; // 这里临时变量,存贮$i的值
	            $a[$i] = $a[$j]; // 第一次更换位置
	            $a[$j] = $tem; // 完成位置互换
	        }
	    }        
	}

冒泡排序是原地排序算法吗?冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为 O(1),是一个原地排序算法。

冒泡排序是稳定的排序算法吗?在冒泡排序中,只有交换才可以改变两个元素的前后顺序。为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,我们不做交换,相同大小的数据在排序前后不会改变顺序,所以冒泡排序是稳定的排序算法。

冒泡排序的时间复杂度是多少?最好情况下,要排序的数据已经是有序的了,我们只需要进行一次冒泡操作,就可以结束了,所以最好情况时间复杂度是 O(n)。而最坏的情况是,要排序的数据刚好是倒序排列的,我们需要进行 n 次冒泡操作,所以最坏情况时间复杂度为 O(n2)。

这个平均时间复杂度推导过程其实并不严格,但是很多时候很实用,毕竟概率论的定量分析太复杂,不太好用。等我们讲到快排的时候,我还会再次用这种“不严格”的方法来分析平均时间复杂度。

插入排序(Insertion Sort)

我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。

如图所示,要排序的数据是 4,5,6,1,3,2,其中左侧为已排序区间,右侧是未排序区间。

插入排序也包含两种操作,一种是元素的比较,一种是元素的移动。当我们需要将一个数据 a 插入到已排序区间时,需要拿 a 与已排序区间的元素依次比较大小,找到合适的插入位置。找到插入点之后,我们还需要将插入点之后的元素顺序往后移动一位,这样才能腾出位置给元素 a 插入。

	function insertSort($arr) {
	    $len=count($arr); 
	    //这个元素 就是从第二个元素开始,到最后一个元素都是这个需要排序的元素
	    for($i=1;$i<$len; $i++) {
	        $tmp = $arr[$i];
	        //内层循环控制,比较并插入
	        for($j=$i-1;$j>=0;$j--) {
	            if($tmp < $arr[$j]) {
	                //发现插入的元素要小,交换位置,将后边的元素与前面的元素互换
	                $arr[$j+1] = $arr[$j];
	                $arr[$j] = $tmp;
	            } else {
	                //如果碰到不需要移动的元素,由于是已经排序好是数组,则前面的就不需要再次比较了。
	                break;
	            }
	        }
	    }
	    return $arr;
	}

插入排序是原地排序算法吗?从实现过程可以很明显地看出,插入排序算法的运行并不需要额外的存储空间,所以空间复杂度是 O(1),也就是说,这是一个原地排序算法。

插入排序是稳定的排序算法吗?在插入排序中,对于值相同的元素,我们可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保持原有的前后顺序不变,所以插入排序是稳定的排序算法。

插入排序的时间复杂度是多少?如果要排序的数据已经是有序的,我们并不需要搬移任何数据。如果我们从尾到头在有序数据组里面查找插入位置,每次只需要比较一个数据就能确定插入的位置。所以这种情况下,最好是时间复杂度为 O(n)。

选择排序(Selection Sort)

选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

首先,选择排序空间复杂度为 O(1),是一种原地排序算法。选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为 O(n2)。

选择排序是一种不稳定的排序算法。选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。

	// 大循环,控制轮数
	for ($i=0; $i < count($a); $i++) {

	    $p = $i; // 假设的最小值的KEY

	    // 小循环,逐个比较后面的值
	    for ($j=$i+1; $j < count($a); $j++) { 
	        if ($a[$p] > $a[$j]) {
	            $p = $j; // 交换key
	        }
	    }

	    // 不相同,则互换位置
	    if ($p != $i) {
	        $tem = $a[$i];
	        $a[$i] = $a[$p];
	        $a[$p] = $tem;
	    }
	}

发布了98 篇原创文章 · 获赞 185 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/xuezhiwu001/article/details/103721738