线性探测法的查找函数_线性查找法

开篇:

对于数据结构及算法的学习在17年时就已经在博客中开了专栏:

3d9eeb24d356765fa8ada95de908268a.png

但是!!!感觉学得有点零散,有c版本的,也有java版本的,没成体系,当然其效果也并没达到自己满意的效果,基于此,这里准备重新开个专栏:

4b5efe5e9f970bad0e3a1aec771d432f.png

准备从0开始构建一个Java版本对于算法与数据结构的学习体系,重点是要“系统”,正好也对于之前所学的算法进行一个重新梳理,并且再将自己的算法功底拔高一下,说实话算法的学习是比较枯燥的,但是它的作用也不言而喻,枯燥在重要性面前是可以咬牙坚持的,然而要想达到拔高的效果那只能一点点从基础知识的夯实再到找到写算法的感觉,最后再来学一些高级的算法,整个过程离不开四个字:“践踏实地”,另外学习它也不能过猛,周期性的一点点来学于我而言是比较舒服的,所以下面全新开启算法之旅啦~~

环境说明:

jdk的版本:

既然是用到Java语言来学习,JDK的版本这里也稍加说明一下,只提一下版本,其它的安装啥的就不过多说明了,网上一大把,这里使用的是使用量最大的JDK8这个版本,而它的小版本呢,先来看一下目前官方是定位到了哪个号:

ce0fc7fa21018b308a6c054c63ad2249.png 

但是!!!这里不用最新的,而是用Java SE 8u251,为啥?因为跟课程保持同步,而对于它的下载包,可以直接google既可:

f2a4bb3472405d40dde6ac3e9f45940b.png

413b8de36557afcbfb38631d6b2df509.png

然后点击时,oracle现在要求登录才能下。。

0869164e66b132571b08d302e4d15e7c.png

发现我木有账号。。那创建一个呗,然后再登录,下载就走起了:

27464f0b9984e6ba1f6c60ac7685ace8.png

当然我司的渣网下了我半天,按向导一步步安装既可,注意!!这里不需要一定得修改JAVA_HOME来指向这个版本,毕竟这个版本下下来仅为了学习,因为IDE在创建工程时可以动态来指定需要本地的哪个JDK版本,到时我们创建工程指定既可。安装成功后,则会在此目录多一个对应的版本,看下我本机安装的好多。。

2d643c87caf61d03e317cf8c48fce04b.png

然后目前我电脑也已经使用此版本了:

758677e077f191f65e6af29615c01678.png 

使用的IDE:

对于Java的IDE,不用说,都是它了:

5f4e93fd44def914594f07e6862738a9.png

然后在创建工程时重点就是选择一下JDK的版本为刚才咱们安装的既可:

c55fa79282b52f8ccff3ffd8b289468f.png

什么是算法:

呃,看到这标题貌似太文绉绉了,但是!!!这里还是将其列出来,为啥?为了能成“体系”,而且多看一眼理论其实也没啥坏事,反而根据自己这么多年学习技术的心得来看,理论+实战才是夯实技术的最佳学习法,当然这是于我这种比较笨的人来说,所以不能对简单的概念说no,下面当一个PPT过一下既可:

一句来来描述:“算法是一系列解决问题的,清晰,可执行的计算机指令”。然后算法它有五大特性:

  • 有限性:
    一个算法不应该是无限循环的,而应该是在有限的时间内能够执行完,但是这个“有限性”的时间不一定非常短,有可能是比较长的。

  • 确定性:不会产生二义性
    它的本质就是描述其算法的计算机指令都是清晰且含义唯一的,比如一个不确定性的例子,就是有一堆数据它时表示一个班集所有同学不同科目的考试成绩,而在设计某个算法时有一个指令是“拿出成绩最高的那个学生”,那么这个算法就具有不确认性,为啥?因为成绩最高是语文还是数学呢?
    但是!!!这个确定性并不是指算法不同的输入其输出也是相同的,举个随机算法为例输入可能是同一个但是其输出会是不同的。

  • 可行性:
    比如说设计一个算法,其中算法的有一个指令是“拿出最大的质数”,这个指令就具有不可行性了,因为质数是有无穷个的,
    以上三种特性其实就是说一个算法并非只是口头说明,最终一定是可以落地的代码的。

  • 输入:
    这个要跟函数的输入区分开,这里是一个更加泛的概念,其实就是算法要操纵的对象就可以说是算法的输入, 而具体表现到程序中,一个算法操作的对象可能定义在一个类中,可能是一个成员变量并非是函数的参数都算是算法的输入。或者有可能算法操纵的对象隐藏在语义当中,比如设计一个算法:生成0这个数字,在函数设计中直接返回0既可,而这个0其实也叫做算法的输入。

  • 输出:
    同理,如果一个函数是一个void,并不代表该算法木有输出,比如说在屏幕中绘制了一个圆,很有可能这个绘制函数木有返回值,但是绘制是一个算法,它的输出就是绘制了一个圆;再比如一个算法就是休眠x分钟,其中x是一个输入,这个函数是一个void没有返回值的,但是这个算法是有输出的,其输出就是程序休眠了x分钟。所以对于算法的输入与输出都需要站在一个更加抽象的角度来看待它,而不能以函数的方法参数及返回值来看待算法的输入与输出。

最简单的算法:线性查找法:

接下来则从一个最简单的算法学起,也就是如题线性查找法,这种算法其实在实际中会经常使用,比如:在一沓试卷中,找到属于自己的那张试卷,这不简单,一张张进行查找呗,如下:

f5a8e68d638b5e19775a384de90fa531.png

这就是典型的线性查找法,回到程序场景中,比如在data数组中查找16这个数字,而此data的形态为:

3a69282b172c76c56f4c97891806c9e8.png

而对于数组的查找so easy,直接拿个下标进行一一查找:

ccae47d8f21f631deff0d25e440d5ae8.png

这块的查找代码其实已经非常熟了,但是这里还是以算法的角度把自己当小白从头来一下,发现此时24不是要找到数字,此时下标i++,形态就变为:

9edaddfc9e66b75e18acdf1ecfac51b0.png

依然不是想要的内容,继续i++:

e1a2d8fd60f73f09324b796da6cc5562.png

再i++:

e3bf5ab95ca07eb2960ca8d0dc190684.png

再i++:

828654d590fd953caabb53ad2019a625.png 

此时就找到了,那么对于这个线性查找算法,它的输入与输出为:

输入:数组和目标待查找的元素16

输出:目标元素所在的索引;若不存在,则返回-1

实现线性查找法:

初步实现:

接下来则来动手实现咱们的第一个小算法,先新建个工程:

87340a4198ac2f8252495bebe27b1902.png

然后实现就不多说了,这里贴一下,人人都会:

public class LinearSearch {    public static void main(String[] args) {        int[] data = {24, 18, 12, 9, 16, 66, 32, 4};        LinearSearch linearSearch = new LinearSearch();        int result = linearSearch.search(data, 16);        System.out.println(result);        int result2 = linearSearch.search(data, 6);        System.out.println(result2);    }    public int search(int[] data, int target) {        for (int i = 0; i < data.length; i++) {            if (data[i] == target)                return i;        }        return -1;    }}

8e82cc3926d94547022af91270b0374c.png

小优化:

对于目前的实现貌似已经完美了,但是这里系统学习算法的同时,还会涉及到Java的设计之美的东东,既然是系统当然各方面细节都得考虑周到,这样高质量的学习才能突破自我进行拔高,所以对于这么简单的一个实现还要对其进行一个设计上的小优化,其实人人也能想到的,就是对于search()这个方法将它声明为static比较好,如下:

19456235f9b9904ac6e7de9ceeeeba4b.png

就像Math类一样,对于它里面的方法直接调用既可,不用生成一个对象再调用,相当于就是一个工具方法,另外为了防止外部来强new,则私有化构造方法既可:

d196f2d4623e880f43bb924179507d56.png

不要看不起这些小细节,往往细节决定xx。

使用泛型:

继续来改造咱们的这个代码,目前编写的search方法是与具体类型绑定的,也就是目前只能对整型数组进行线性查找,接下来则将其改为泛型通用化它,也比较简单:

61636d678bf858e68e7b150b07a9bfdd.png

但是此时报错了,看报啥错:

e6b9d9999114adbcd7b4ac5178e7bb72.png

这里就需要复习一下泛型的一个知识了:“在使用泛型时,不可以是基本数据类型(boolean、byte、char、short、int、long、float、double),只能是类对象,所以需要使用对应的基本数据类型的包装类(Boolean、Byte、Character、Short、Integer、Long、Float、Double)”,所以修改一下:

f14648c16292aa45f4a22bf64e8a51f2.png

另外对于对象类型的比较就不能用==了,因为比较的是地址,这都是java基础语法这块的东东了,不多了,修改一下:

087d1d700c06bf5413c41a21f05024aa.png

其运行结果是一样的,当然,关于equals方法对于自定义类来说需要自己写逻辑,但是!!!对于线性查找的算法本身来说不是它考虑的范围,equals怎么写是跟业务逻辑相关了。

使用自定义类测试我们的算法:

接下来咱们来定义一个自定义的类,让其也能使用咱们编写的线性查找算法,这块也是比较熟的写法了:

a3853e2ce787f362ee8e48e902ecffa4.png

此时咱们直接使用算法查找肯定是有问题的,试一下:

3b3609f391022b90957d8537bbde883b.png

这是因为Student.equals()方法是比较的地址而非内容,所以需要自己来重写eques()方法来定义比较的逻辑,如下:

7fc8a1484dcbec30be2646048b34bc88.png

注意,这个代码写得是有问题的,因为equals的Object的类中的方法,它的定义是:

a73fb03d2e56430acb2dda18bd5ed0f5.png

这点是比较容易犯错的,需要注意,这里修正一下:

ddb17b198ebe34a31075403815942d7d.png 

当然啦,实际开发中都是用IDE自动来生成的,不用关心这些小细节,但是!!!学习的意义不一样,越细对自己的成长越有利,所以不要畏惧细节~~下面来实现一下:

 @Override    public boolean equals(Object o) {        Student another = (Student) o;        return this.name.equals(another.name);    }

但是!!!这个写法是不严谨的,前面还需要加一些判断,因为不判断的话有可能强转成Student会失败的,所以:

    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (o == null || getClass() != o.getClass()) return false;        Student another = (Student) o;        return this.name.equals(another.name);    }

接下来再来运行一下:

9d58943f84d06712b98b28436c01f871.png

循环不变量:

接下来基于咱们写的这个非常简单的小算法引出一个非常重要的概念----循环不变量,理解它能对咱们理解算法以及写出正确的算法都很有用,但是呢理解它也是有一些不那么简单的,循环我们每天都在接触,只是提练出这个概念,所以来了解一下它,先来回忆一下咱们写的线性查找算法:

f360966f110d268eb24fd048cb0834c6.png

也就是每一次循环“开始”时都要确认data[i]是否是目标,而在循环体中只有个if条件,当if条件木有满足循环体执行完时,此时就可以确认data[i]不是目标了:

a5a30f824868e420213b1c71e8c7f9eb.png

接下来就开始绕脑了。。这个“确认data[i]不是目标”其实也就是说:

2172a484a9847262dd15df2373341959.png

注意:此时用到了一个[]闭区间,也就是包含左右边元素,嗯,有道理,既然确定了data[i]不是目标,那不代表i之前的所有元素也不是目标,其中包含i这个位置,这句话木有毛病。类似的对于确定目标时也可以这样理解:

b7619b2b6e944390fc20c76af0923979.png

嗯,再思考一下,等于开始循环的时候,正在循环的i位置还没有判断呢,那不就是它之前的所有元素都已经确定了木有找到目标了嘛,所以是[0,i-1]这个区间可以确定是木有找到目标的嘛,当然这块也可以用闭开区间来表示:

11fb945772dc1d3bd05e9c56a78aa10d.png

感觉说了一堆费话呀,其实不是的,这是有目的的,因为未来算法学习之路会用到这种开闭区间的思想来思考我们的算法,目前因为算法太简单的再配上这么绕脑的“概念”感觉有点牵强,但是也是打好算法基础的一个小点,好,接下来要引出主角了,“循环不变量”:

00c1948be1c4ee5a6300977f26789d14.png

11a6de151aafb943df11312048d35842.png 

其实上面这些也就是证明算法的正确性,对于写好循环来说也就需要清楚的知道循环不变量是什么,提前有意识的来了解循环不变量也是咱们写出正确代码的一个前提,也是未来不断探究算法的一个比较重要的基础,目前先有个感性的认识,待未来再来体会它,总之:

对于一个循环来说需要定义清楚它的循环不变量,而循环体就是用来维护这个循环不变量,这些都是写出正确代码的关键点,另外还有一个就是对于函数来说需要定义清楚函数的功能,而这个“清楚”其实指的是需要定义清楚它的输入与输出,拿咱们的线性查找来说:

5266e11cd981b1f541874a0118f868f8.png

简单的复杂度分析:

说到算法的复杂度,一般都能想到时间和空间这俩,其中时间复杂度O在实际面试当中也会被经常提及,但是呢这块也是非常理论化的一块,经常自己也分析不太明确,所以借此机会好好巩固一下。

其实算法的复杂度的分析就是用来衡量一个算法的性能的,而这种分析方式是一种站在抽象的角度来考量的,也就是说并非用一个非常标准的公式来衡量它,比如有一个方法一执行就能精准的知道算法的性能了,因为实际有可能在面对一个算法的选择时需要用一种抽象的思维来对它进行一个性能的考量而非得先自己写代码来精准的研判它的性能再来进行算法的决策,所以说复杂度分析就是用来解决这种问题的,直接自己按照一定的标准从思想层面就可以大致算出算法的好坏。

这里还是以咱们的这个算法为例,来看一下它的复杂度:

e205d7913040d485dfd155231ff7e0b1.png

在复杂度分析之前需要明确以下几点:

1、复杂度的分析是分析最差的情况,而非是最好的情况。

2、它是算法运行的上界,也就是说算法的性能最高就是我们所分析的样子了,不可能比它还要差了。

好,那怎么来分析咱们写的这个算法的复杂度呢,很显然数组的大小决定了算法的性能,所以可以用n=data.length来表达数组的大小, 那就可以开始分析了:

a、初略的感觉貌似就是进行了一个数组的遍历嘛,最坏不就是遍历n遍,那么咱们算法的运行时间可以用T来表示的话,那是不是可以说咱们这个算法的T=n?

b、但是定睛一看,貌似又太对呀,因为循环中是做事情了呀,有个判断:

cfb460ce0faa467be3e0b70497ee631b.png

所以此时算法的时长T=2n?

c、再定睛一看,貌似又不太对呢,对于判断条件貌似不能只看成一步:

5da04a7069517a7f04616d21fa5e15c5.png

data[i]算一次寻址操作,得再分一步,那是不是T=3n了?

d、那同理,对于for循环也不能看成是一个整体:

549f26348e65cacba0a2d6f06164d0bc.png

i

e、哦,感觉还是不对,貌似i++也得拆成一步嘛,那T=5n?

f、再细看一下,貌似还是不太准,貌似这块还要拆:

6db22c73346ecc5c6c1bc76d598304ca.png 

T=5n+2

g、感觉还是分析得不准确,因为对于java底层的汇编指令貌似还有其它步骤,而汇编貌似还得拿出机器指令来,机器指令还得依赖于机器运行的CPU架构。。。。。这样就没完没了的,另外对于T=5n+2它的时间单位是啥其实也不清楚,因为都得由机器运行时间来决定。

所以对于目前咱们的这种分析算法的时间复杂度来说就不太可行了,抛开具体的细节,其实衡量算法的性能只需要知道它跟data的大小n成一个正比的关系就好了,此时O(n)就出现了,此时咱们就可以将T=5n+2的具体常数用它来表示:

a8f15b2e4daccfe3a4d9e7f61cee0436.png

但是在算法复杂度计算时,常数是不重要的(也就是c1和c2)复杂度描述的是随着数据规模n的增大,算法性能变化的趋势(注意这句话之后会论证)。如果说一个算法的复杂度是O(n),那么你就能知道这个算法的时间性能和它所对应的数据规模n之间成一种线性增长的关系。

那下面再来看一下假如有2个算法的时间复杂度分别为:

9dc2351782e6f06a4f9944214ecdeb52.png

根据算法复杂度理论,“常数不重要的”,所以用O表示法如下:

fa2055925752fbdc0c46f6eb25137f19.png

对于熟悉复杂度分析的一看便知,很明显T1的性能要优于T2,因为T1是线性的,而T2是指数:

ba1416817aba6e1cb384f6b7cd8c169b.png

这是为啥呀,很明显T1的常量是10000,而T2只有2,凭什么T1的性能要优于T2,这其实是:

422f12c18bd6d6d3c026c6bf937698df.png

也就是说当n大于这个临界点时,T1一定是小于T2的,那么这个临界点是多少呢?其实也可以求解出来,也就是求这个不等式:

522cba38f3b8c3984f6eb6181b40a375.png

两边同时除以2n,那么就为:5000 < n,所以:

6e16fd0004ae7731393986279e6153ff.png

也就是当n>5000时,T1随着数据规模n的增大,算法性能的变化趋势。

常见的时间复杂度:

接下来学习一下常见可能会用到的一个时间复杂度,先有个大概印象既可。

09d4ab7cffdaa088d0999e05ceb900a4.png

这个我们已经学过了,它是线性增长的,如咱们写的线性查找算法:

366f5444db0df530922d4af9c5daac3e.png

e994d340face7b25d3ff29fa82fe17b0.png

这个也是很常见的一个复杂度,典型的就是双层for循环,比如要从一个数组中的元素可以组成哪些数据对,其代码可能是这样:

0d0df959c4fb1b516562fe74a697a760.png

但是其实这个算法是O(?²/2)的,因为内层循环的j是i+1开始的,但是!!!还是那个理论,对于常数是不计在算法复杂度的计算当中的。

再比如遍历一个n * n 的二维数组,那就不用多说了,妥妥的是O(?²)了:

a634164d41420d4bdd838c75f0574c6c.png 

另外有一个注意事项,就是需要理解这个n代表是啥,对于这个程序而言很显然n代表的是某一个数组的维度是n,下面再来对比看另一个类似的代码:

a9454ab270719adb4b57a8dd0071707e.png 

那你认为它的复杂度是多少呢?由于此时的n表示的数组的元素为n,那么它的复杂度就变为了O(?)了,所以理解n是谁是非常重要!!!

 2699ef3a124c3f5c4dacf0fc35d08a6a.png

这种也是很常见的一种,它比 O(?)性能还要好,比如说计算一个数字n的二进制位数,其计算方法很简单:

5d18e640f9b6b6dc2f3880fad62af9cd.png 

所以其代码为:

354eb457c16f41c910a6cff9953043da.png

其中对于log它是咱们数学当中的对数,关于啥是对数说实话有点忘了,百度一下:

d55a3d5021d56a86ec30c2efc59d0517.png

也就是其实上面的这个O(log2ⁿ),同样的假如说要计算一个数字n的十进字呢?此时的算法是一样的,但是其复杂度就变为了O(log10ⁿ),那问题来了,凭啥它们都叫O(logn)的复杂度呢,其实这里可能稍加算法一下,对于对数来说有一个换底公式,如下:

eb83ebe79b979ac92952aba8d948c879.png 

回到咱们的场景来说此等式就成立了:

5fea67aa2744b2676d3baf45ddd9ed86.png

而发现没,其实log2ⁿ=(1/log10²) * log10ⁿ,很明显两者之间就差了一个常数,对于算法理论来说常数可以忽略,那不log2ⁿ和log10ⁿ可以认为都差不多时间复杂度的,将底去掉,最终不就是O(logn)级别了,所以记住!!!平常如果算法复杂度是底数级别的,不用带底数。另外从这个例子中也道出了一个注意事项:就是不要数循环的个数来算算法的复杂度,得根据循环体中的实现来决定。

b654e88e0ae2f400c6080d4a9cdeb944.png

先来回到这样一个数学题:求数字n的所有约数,呃,啥是约数,忘了,度娘走起:

1aab732a8622ee3eb0a4cec17cbeec09.png

哦,就是能被整除的数都叫一个n的约数,那这算法简单,循环遍历既可,如下:

1b1bd9cdf8fc474642d50193a72fcfb5.png

那它的复杂度是多少呢?很明显是O(n)级别的,因为就是循环了n次嘛,但是这算法性能不是太好,可以将其优化,其实可以一次算出两个约数出来:

f22d1a0a523291c30519a2c31adf38f5.png

此时它的时间复杂度就降为552160228dc1319152741c3243881f8e.png了。

6cf86f54ca0639dd8b3953e2a3eb09a1.png

比如要算出长度为n的二进制数字,每位会有0或1出现,那复杂度就是O(2ⁿ)啦,俗称指数级别的复杂度,非常恐怖的一种,设计算法需要避免。

e0aa0fcf3fc0e09d39a3ac2bea0793bd.png

比如计算出长度为n的数组的所有排列,此时就需要用到数学里的全排列了,回忆一下:

7d8788a74b091cf4644acfc5d1989865.png

根据公式也能看出它是阶乘级别的,也是性能最慢的一种。 

692a19de57335aa722b5b937e45d334c.png

这种是性能最好的,比如求一个数是否是偶数:

4e524cfc6a280c0049a0c8cb3f00d8f6.png

由于n不管多大,其计算的性能是一样的,所以它是一种常数级别的算法,另外有个注意点:假如一个算法是由多个常量级别的组成,最终也是O(1),因为还是那句话,常量不重要!!!

上面就是非常常见的时间算法复杂度,那这些的性能谁好谁坏呢,下面一张图了解一下:

a2dfa02e2d5ac8f9c0087f2d82f36b84.png 

其中重点是要对于O(logn)进行说明一下,它是非常快的一种算法复杂度,未来算法的学习会大量应用到,也是算法优化的一个目标。

空间复杂度:

另外实际面试时可能还会被问到空间复杂度,它的表示符号跟时间复杂度是一模一样的,拿咱们所写的程序为例看一下它的空间复杂度是多少呢?

6598e41fd7a9e13b2a7211afd9ade40b.png

很明显咱们这里没开啥空间,所以它的空间复杂度就是常数级别的O(1),但是!!!!实际算法优劣考量还是主要是考察时间复杂度,因为如今计算机空间越来越大,相比时间来说没这么值钱,所以有很多产品的优化都是用空间去换时间的,未来算法的学习也主要是看时间复杂度,这一点需要明确。

测试算法性能:

在上面已经了解了算法的时间复杂度之后,那对于我们写的程序也有必要来测试一下它的性能,不然只是纸上谈兵,那看一下咱们目前的代码,貌似不足以进行性能测试:

81f78b2f978400ff03f85078e0d7f1df.png

所以,有必要来模拟生成比较大的数据便于进行算法的性能测试,所以这里新建一个辅助类:

17f1c4470556fe4781a3e1b484817843.png

public class ArrayGenerator {    private ArrayGenerator() {    }    public static Integer[] generateOrderedArray(int n) {        Integer[] array = new Integer[n];        for (int i = 0; i < n; i++) {            array[i] = i;        }        return array;    }}

这里也没啥好解释的,接下来咱们来使用一下:

5b81a28c4a574c97fdb3598995c81818.png

运行:

8c7c14fd4bf1a91b154e62329e5ed1d8.png

大概是5ms的样子,相当的快,但是!!感觉这种测试貌似也说明不了啥,得要用不同量的数量进行多次测试才能说明问题,对于线性查找算法而言它的时间消耗会n的增大而增大,所以下面咱们改一下程序,将10万改成100万:

7a8b2d60d9fe44bb76927dba5ecf0f6e.png

那如果改更大呢,比如1亿个,貌似这样开1亿个连续空间可能都有点费劲,所以咱们换一种测试思路,就是让搜索多执行几次就可以了:

6ce7924b572952be2b2eea292f460694.png

那此时将n再加大,由100万改为1000万:

a9b0f87f09d96faa6a704a771e58d823.png

但是咱们目前木有一个针对不同的n的时间对比,所以咱们再来个循环,将每次n的测试的结果都能输出来在控制台上可以直观的感受到它们之间的时间差:

public class LinearSearch {    private LinearSearch() {    }    public static void main(String[] args) {        int[] dataSize = {1000000, 10000000};        for (int n : dataSize) {            Integer[] data = ArrayGenerator.generateOrderedArray(n);            long startTime = System.nanoTime();            for (int k = 0; k < 100; k++) {                LinearSearch.search(data, n);            }            long endTime = System.nanoTime();            double costTime = (endTime - startTime) / 1000000000.0;            System.out.println("n = " + n + ", 100 runs : " + costTime + " s");        }    }    public static int search(E[] data, E target) {        for (int i = 0; i < data.length; i++) {            if (data[i].equals(target))                return i;        }        return -1;    }}

b2769ca8ecf981fb873c5b909d240740.png

好,咱们来看一下输出结果,两次n之间是10倍的关系,而runs差不多是9倍的关系,其实大概理解是10倍,由于我的机器性能不是太好,在数据量过大时运行没那么快,但是有可能你的机器性能好运行出来的时间会超过10倍的,但是没关系,因为这里是对O(n)算法的一个直观的理解,它的时间性能是随着n的增加而增加的。

结语:

这是重新体系学习算法的开篇,虽说是非常非常简单的一个线性查找算法,但是云引出了很多很多关于算法非常的概念,还是那句话,万丈高楼平地起,重视基础,重视细节,对我来说是高楼平稳的建立的一个非常重要的原则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值