基础数据结构-如何衡量算法好坏
假设面对一个问题:现在提供了一个数组 nums[ ] ,与一个目标值 target,我们需要从该数组中查找该目标值,若能成功找到则直接返回该目标值在数组中的 索引值,若找不到则返回 -1 。
此时我们可以简单地想到两种查找方法:
- 二分查找法
- 线性查找法
虽然线性查找法看起来更为简单,而且对于数组内的元素都不需要有升序的要求,但是如果从执行效率上对比的话,会发现二分查找法比线性查找法的效率要高很多很多。
而我们该怎么去证明这个执行效率的对比情况呢,我们分别来分析下两种算法的运行流程:
-
事后统计法
各跑一遍两种算法,查看它们的运行时间,这是最直接的方法。虽然这种做法较为直接,但是有一定的缺点:①比较依赖测试数据,需要人工把测试数据准备好,但是这通常会花费很多精力。如果测试数据准备得不好、不充分,那么是测不出两种算法的好坏的。比如如果我们给这两种算法提供的测试数据都是很小的数组,那么是体现不出来它们运行的快慢,或者差距并不大,只有在数据量规模较大时才能比较出来。
②比较依赖硬件环境,在不同配置的电脑下运行速度可能不相同。
-
事前分析法**(main)**
该方法不要求实际执行算法,而是通过分析的手段去预估算法的执行时间。我们将以这两种算法为例进行分析,但是有两个前提:只分析算法最差的执行情况(最好、平均情况暂不考虑);简化分析过程,假设每行语句执行时间是相同的①线性查找
对于线性查找法来说,最差执行情况就是找不到结果并返回-1的情况,需要执行全部的代码语句。确定了最差情况后就需要分析该算法一共执行了多少行代码:
首先需要设定数组中数据元素的个数为n,然后分析for循环中各条代码语句需要执行多少次,最后进行累加。
可以算出最终总执行次数为 3n+3 次,发现它与数组中数据元素的个数是相关的。②二分查找
对于二分查找的最差执行情况与线性查找的情况不同。假设此时要在数组 *[2,3,4,5]*中查找目标值 1或 6,虽然在数组中的最左边与最右边都无法找到,但是实际上向数组的最左边查找或最右边查找时运行的语句总数是不相同的,实际上也是向右侧查找没找到时的运行效率更差。所以我们就需要以向右查找没找到的情况为最差执行情况。
接着看算法中各行语句的执行成本:中间while循环的逻辑较为复杂,我们需要结合二分查找的运行逻辑进行分析。
当元素个数为4~7时,循环次数为3次;当元素个数为8~15时,循环次数为4次;当元素个数为16~31时,循环次数为5次;当元素个数为32~63时,循环次数为6次,以此类推。
可以看出元素个数与循环次数之间存在一种规律:floor(log2n\log_{2} nlog2n) + 1 (floor函数用于向下取整,化简掉小数)。设循环次数为L,再去计算所有语句的执行次数
可以算出最终总执行次数为 (floor(log2n\log_{2} nlog2n) + 1) * 5 + 4 次。③对比
接下来就可以来对比两个算法
假设元素个数比较少,只有 4 个元素。此时带入线性查找法,可以算出最终执行次数为 3 * 4 + 3 = 15 次,假设每行代码执行使用的单位时间为t,那么总耗时就为 15t 。
同样带入到二分查找法,算出执行次数为 (floor(log22\log_{2} 2log22) + 1) * 5 + 4 = 19 次,总耗时为 19t 。
虽然此时好像是线性查找法占有,但是当元素个数增加到 1024 个时,线性查找法总耗时为 3075t ,而二分查找法总耗时仅为 59t 。
所以随着数据量增大,二分查找法的执行效率是明显更高的。可以利用画图软件来进行验证: