算法详解(〇)--算法性能指标

本篇文章是这个专栏的第一篇文章,主要介绍一个这个专栏,同时介绍学习使用算法的分析指标。

专栏目的

不管是社招还是校招,数据结构与算法都是面试中非常高频的考点。而排序算法整体难度适中,很适合手写,所以有不少面试官都喜欢让人手写各种排序算法,并进行分析。同时在实际的开发任务中,排序也是很重要的一环。所以学习、分析算法是程序员非常重要的一课。

时间复杂度

时间复杂度

时间复杂度一般用大O表示,具体的概念就不展开细说了,这里说三个使用的分析方法:

  1. 只关注循环执行次数最多的一段代码。 比如下面这段求和代码只要关注for循环中的求和计算,也就是n次循环,两个变量的赋值是常数级的,对n不会有质的改变,所以复杂度为O(n)
 fun(int n) {
   int sum = 0;
   int i = 1;
   for (; i <= n; i++) {
     sum = sum + i;
   }
 }
  1. 加法法则:总复杂度等于量级最大的那段代码的复杂度。比如一段代码中有一个n层循环和一个 n*n 层循环,这时时间复杂度是O(n²),而不是O(n²+n),因为n²和n不是一个量级的,我们只要关注量级大的即可

  2. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。举个栗子,假设函数A fun_a()的时间复杂度是O(n),函数B fun_b() 在一个m层循环中调用了函数A,那么函数B的时间复杂度就是O(n*m),若m=n就是O(n²)

最好、最坏情况时间复杂度

最好情况时间复杂度就是,在最理想的情况下,执行代码的时间复杂度。比如说在一组数中查找某个数,在最理想的情况下,要查找的变量 x 正好是数组的第一个元素,这个时候对应的时间复杂度就是最好情况时间复杂度。

同理,最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。同样是查找一个数,如果数组中没有要查找的变量 x,或者他刚好在数组末尾,这时我们需要把整个数组都遍历一遍才行,所以这种最糟糕情况下对应的时间复杂度就是最坏情况时间复杂度。

平均情况时间复杂度

最好和最坏情况时间复杂度都是比较极端的情况,我们当然不能直接拿他们来衡量算法的整体情况,这时就可以采用平均情况时间复杂度来表示。我们以数组查找为例,简单介绍平均复杂度的计算。
要查找的变量 x,要么在数组里,要么就不在数组里。这两种情况对应的概率统计起来很麻烦,我们假设在数组中与不在数组中的概率都为 1/2。另外,要查找的数据出现在 0~n-1 这 n 个位置的概率也是一样的,为 1/n。所以,根据概率乘法法则,要查找的数据出现在 0~n-1 中任意位置的概率就是 1/(2n)。将每种情况的遍历次数乘以对应的概率再累加,结果为(3n+1)/4,也就是O(n)。
除了平均时间复杂度,还有均摊时间复杂度,实际应用较少,有兴趣可以自行研究一下。

空间复杂度

空间复杂度和时间复杂度的概念类似,但是要简单一些,因为常见的空间复杂度只有O(1),O(n),O(n²),而常见的时间复杂度除了这三个还有O(log n),O(n*log n)等较复杂的情况。这里就不详细说明空间复杂度了,一般都可以较简单的分辨出来。

排序算法的执行效率

算法的时间复杂度和空间复杂度时衡量算法的重要指标,对于排序算法,我们还可以有更加细致的分析,来确定排序算法的性能。

1. 最好情况、最坏情况、平均情况时间复杂度

我们在分析排序算法的时间复杂度时,要分别给出最好情况、最坏情况、平均情况下的时间复杂度。除此之外,你还要说出最好、最坏时间复杂度对应的要排序的原始数据是什么样的。
为什么要区分这三种时间复杂度呢?第一,有些排序算法会区分,为了好对比,所以我们最好都做一下区分。第二,对于要排序的数据,有的接近有序,有的完全无序。有序度不同的数据,对于排序的执行时间肯定是有影响的,我们要知道排序算法在不同数据下的性能表现。

2. 时间复杂度的系数、常数 、低阶

我们知道,时间复杂度反映的是数据规模 n 很大的时候的一个增长趋势,所以它表示的时候会忽略系数、常数、低阶。但是实际的软件开发中,我们排序的可能是 10 个、100 个、1000 个这样规模很小的数据,所以,在对同一阶时间复杂度的排序算法性能对比的时候,我们就要把系数、常数、低阶也考虑进来。

3. 比较次数和交换(或移动)次数

基于比较的排序算法的执行过程,会涉及两种操作,一种是元素比较大小,另一种是元素交换或移动。所以,如果我们在分析排序算法的执行效率的时候,应该把比较次数和交换(或移动)次数也考虑进去。

排序算法的内存消耗

前面讲过,算法的内存消耗可以通过空间复杂度来衡量,排序算法也不例外。不过,针对排序算法的空间复杂度,我们还引入了一个新的概念,原地排序(Sorted in place)。原地排序算法,就是特指空间复杂度是 O(1) 的排序算法,也就是不需要额外的空间即可完成数据的排序。

排序算法的稳定性

仅仅用执行效率和内存消耗来衡量排序算法的好坏是不够的。针对排序算法,我们还有一个重要的度量指标,稳定性。这个概念是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。我通过一个例子来解释一下。比如我们有一组数据 2,9,3,4,8,3,按照大小排序之后就是 2,3,3,4,8,9。这组数据里有两个 3。经过某种排序算法排序之后,如果两个 3 的前后顺序没有改变,那我们就把这种排序算法叫作稳定的排序算法;如果前后顺序发生变化,那对应的排序算法就叫作不稳定的排序算法。
稳定排序算法在某些场景有很大的用处,比如,给电商交易系统中的“订单”排序。订单有两个属性,一个是下单时间,另一个是订单金额。如果我们现在有 10 万条订单数据,我们希望按照金额从小到大对订单数据排序。对于金额相同的订单,我们希望按照下单时间从早到晚有序。这时就可以通过先对时间排序,再用稳定排序对金额排序,可以很方便完成这一要求。

posted @ 2020-08-25 21:00  PPPPu  阅读(627)  评论(0)    收藏  举报