noip数据结构与算法 之 基础常识 算法复杂度分析

noip数据结构与算法 之 基础常识 算法复杂度分析

算法复杂度分析是NOIP的基础知识,接触算法复杂度之前,你应当先理解什么是算法。关于算法的定义如下:

算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。

可能你看不懂定义所说的算法,你可以简单的把算法理解成你敲出来的一个函数,用来解决某个指定问题;当然你的整个程序也是一个算法,是用来解决题目要求的问题。大致理解了算法,我们就要去考虑如何评判一个算法。评判一个算法的标准有两个,即时间复杂度和空间复杂度。接下来我们详细介绍一下两个标准。

时间复杂度

所谓时间复杂度是指执行算法所需要的计算工作量,简单理解就是计算机底层要执行多少次的操作。我们可以这么想,每一个基础操作执行的时间一般是相似的,在纳秒级并且极差很小。因此执行的操作次数就和时间成正比关系。而一般情况下程序的操作量跟问题提供的数据规模会成函数相关。假设这个函数是f(n),那么O(f(n))即表示程序的平均时间复杂度(这只是一种简易的记法,关于算法复杂度有严格的数学定义,参见《算法导论》一书,此处不做赘述)。比如我们有一个循环的函数代码如下:

void loop(){
    for(int i=0;i

我可以直接告诉你这个loop()函数时间复杂度是O(n)的,也就是f(n)=n。但是为什么呢?我们可以很简单的算出doSomething();这个语句被执行了n次。而我们的f(n)表示的就是对于某个函数,它执行了多少次基础操作。这里我们很容易看出doSomething()执行了n次,因此f(n)=n。

那么现在我有另一个函数,这个函数的for循环了sqrt(n)次,代码如下:

void loop(){
    for(int i=0;i

这个函数的时间复杂度是什么呢?很简单是O(\sqrt{n}),因为doSomething();语句被执行了\sqrt{n}次。

当然我们这里说的O(f(n))是不严谨的,一般的算法时间复杂度都是根据具体的操作并使用数学归纳法严格证明的,其中的f(n)是一个复杂的函数逻辑。而我们所述的O(f(n))是其中简单的趋势。

如果一个程序只有一个for循环的操作(如第一次的例子中的loop()),我们可以说函数是线性的且f(n)=kn,k表示某次循环过程中的基础操作数。以这个例子为例:

void loop(){
    for(int i=0;i

你可以很轻易的知道f(n)=2n。但是我们只考虑趋势,我们就只考虑n而忽略之前的k=2,我们说这个算法复杂度是O(n)的。为什么要这么表达?因为我们知道这个函数是线性的,我们不需要关心具体常数是多少。我们用O(n)表示这个算法的时间复杂度与数据规模呈线性相关。O(2n)、O(3n)我们都当成O(n)看待,这样很简单不是吗?或者用另外一个思路去理解,常数K在我们实际的运行过程中是很小的,它可能只有1-100,而我们的数据规模n可能是1000、10000甚至100000000。常数k相对于数据规模n太小了!以至于我们甚至可以忽略不计。

同样的思维方式可以应用于接下来这个函数的时间复杂度分析:

void loop(){
    for(int i=0;i

这个函数执行了n^2+n次的doSomething();f(n)=n^2+n,采用刚刚的思维方式,该算法的时间复杂度只需考虑最高次项,即是O(n^2)。这很好理解,因为当数据规模n很大的时候,n^2的增长速度是远大于n的,因此我们可以忽略n的存在。这也就是说,对于f(n)是多项式的情况,我们只需要取其最高次项就好,其余低次项一概忽略。这样非常简便,不是吗?

这样来说,常见的算法时间复杂度有O(n)、O(n^2)、O(n^3)、O(\sqrt{n})、O(2^n)、O(logn)、0(n!)。但是呢,不同的时间复杂度之间可以相互组合。更专业的说法是算法复杂度的函数f(n)是线性的。通俗来讲就是说,当我的T算法和S算法属于并列关系,我的总算法时间复杂度是O_T+O_S;T算法和S算法属于嵌套关系,我的总算法时间复杂度就是O_T·O_S。当然对于某些情况我们相加之后的结果可以忽略加法计算的部分(比如多项式,我们可以直接忽略低次项)。但是嵌套的情况一般不会有舍去,常见的组合算法时间复杂度有O(nlogn)、O(n^2logn)、O(n\sqrt{n})、O(n·2^n)、O(\sqrt{\sqrt{n}})等。

算法的时间复杂度可以用来判断该算法在给定规模的数据下会不会超时。一般地,O(n)算法可以接受n\leq 10000000的数据规模,同样计算O(nlogn)的算法只能接受n\leq 10000的数据规模,O(n*n)的算法只能接受n\leq 1000的数据规模。有了时间复杂度的估算,我们就可以在做题时对程序进行初步的分析,判断我们的思路是否能完全解决这个问题。

空间复杂度

空间复杂度,顾名思义就是执行某算法所需的内存空间大小。和时间复杂度类似,也和数据规模呈正相关。但是我们在评判一个算法的时候,对空间复杂度的考虑一般没有时间复杂度那么高。相对来讲,空间复杂度更容易降低,而且很好计算。

我们一般不需要对空间复杂度做函数分析,也不需要通过数据规模大致估算这个复杂度下算法是否能完成任务。我们只需要考虑我们定义的变量占用字节数,以及定义了多少个。

比如我有一个1000000长度的int类型数组,它的空间占用大致为:

一个int类型变量占用4bytes的内存空间,那么1000000个int变量就是4000000bytes的内存空间。以千进制估算,那么这个数组大概就是4000Kbytes,大概就是4Mbytes的内存空间占用。现在一般问题的空间要求都是256M,512M之类的,所以一个1000x1000的int型数组是完全没有问题的。严格而言大概5000x5000的int型数组,就是我们内存范围的极限了,也就是大概100M空间(这个说法是相对于空间要求128M的题目而言,空间要求512M的题目,开10000*10000的数组也是没问题的)。

以上是对于时间复杂度和空间复杂度的介绍。通过这两个复杂度的分析,我们就可以大致推算出这个算法在解决问题时候的效率。通过这个推算,我们就可以了解这个算法应该可以通过题设的那些数据范围,拿到多少分。因此在实现算法之前,先对算法的核心部分进行复杂度分析,然后确定自己能够拿分的范围,是极其有必要的。也是在NOIP赛场上得分的关键。

你可能感兴趣的:(noip数据结构与算法,之,常识)