算法:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或者多个操作。
算法的五个特性:
- 输入输出:算法具有零个或多个输入,算法至少有一个或者多个输出。输出的形式可以是打印也可以是返回一个或者多个值。
- 有穷性:指算法在执行有限步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。
- 确定性:算法的每一步骤都具有确定的含义,不会出现二义性,算法在一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果。算法的每个步骤被精确定义而无歧义。
- 可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。可行性以为着算法可以转换为程序上机运行,并得到正确的结果
算法的设计要求:
- 正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案。算法正确性从低到高有4层含义:(1)算法程序没有语法错误;(2)算法程序对于合法的输入数据能够产生满足要求的输出结果;(3)算法程序对于非法的输入数据能够得出满足规格说明的结果;(4)算法程序对于静心选择的,甚至刁难的测试数据都有满足要求的输出结果。优良的算法设计通常至少要满足到第三层。
- 可读性:算法设计的另一目的是为了便于阅读、理解和交流。
- 健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生莫名其妙的结果
- 时间效率和存储量低:设计算法应尽量满足时间效率高和存储量低的需求
算法效率的度量
1.事后统计方法:这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同的算法编制的程序运行时间进行比较,从而确定算法效率的高低。
2.事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。
一个用高级语言编写的程序,在计算机上运行时所消耗的时间取决于以下因素:
(1)算法采用的策略、方法;
(2)编译产生的代码质量;
(3)问题的输入规模;
(4)机器执行指令的速度
测定运行时间最可靠的方法就是计算对运行时间有消耗的基本操作的执行次数,运行时间与这个计数成正比。在分心程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤。
int sum =0; int n =100; for(int i = 0; i <n;++i) { sum += i; }
像上面这段代码,其输入规模为n,那么时间消耗总量(消耗时间的基本操作的次数,一次基本操作的时间消耗,假设它为O(1),我们把它记为f(n),忽略循环头的时间消耗。假设求和这样的基本操作时间消耗为1,那么在上述算法中,这个求和过程被重复执行了n次,那么f(n) = n;
在分析一个算法的运行时间时,重要的是把基本操作的数量与输入规模关联起来,即基本操作的数量必须表示成输入规模的函数。
函数的渐进增长
函数的渐进增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐进快于g(n)。
判断一个算法的效率时,函数中的常数和其它次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。如果可以对比几个算法的关键执行次数函数的渐进增长性,基本就可以分析:某个算法,随着n(输入规模)增大,它会越来越优于另一算法,或者越来越差于另一算法。这就是事前估算方法的理论依据,通过时间算法复杂度来估算算法的时间效率。
算法的时间复杂度
算法时间复杂度:在进行算法分析时,语句总的执行次数T(n)是关于问题规模(输入规模)n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作T(n) = O(f(n))。它表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
这样用大写O( )来体现时间复杂度的记法,我们称之为大O记法。
一般情况下,随着n增大,T(n)增长最慢的算法为最优算法。O(1)俗称为常数阶,O(n)俗称线性阶,O(n^2)俗称为平方阶。
推导大O阶方法
1.用常数1取代运行时间中的所有加法常数
2.在修改后的运行次数函数中,只保留最高阶项。
3.如果最高阶项存在且不是1,则去除与这个项相乘的常数,得到的结果就是大O阶。
注:顺序结构的时间复杂度是O(1),当然前提是这些都是基本操作,例如赋值啊,比较啊等等。
单纯的分支结构(不包含在循环结构里)的时间复杂度也是O(1),前提也是分支的操作都是基本操作。
循环结构的时间复杂度就不一定了,单纯的循环结构就向上面举的求和的例子,其时间复杂度就是O(n)。
要确定某个算法的阶次,我们常常需要确定某个特定语句集运行的次数。因此,我们要分析算法的复杂度,关键就是要分析循环结构的运行情况。
对数阶
int count = 1; while(count < n) { count = count*2; ... //时间复杂度为O(1)的程序步骤序列 } 那么这个算法的时间复杂度是多少呢?显然对这个算法来说其输入规模的最大值是n,count是每一轮循环的输入规模,那么当count渐进增长超过n的时候基本操作语句总共执行了多少次呢? 观察发现,count每一次循环都是上一次的两倍。 那么假设执行的次数为x,输入规模为变量n,可以得出x和n的关系是: 2^x = n; 计算算法时间复杂度,我们要求的是执行总次数关于输入问题规模的函数,因此,对上述等式进行变化得: x = log2(n) 即f(x) = log2(n); 接下来推导大O记法。 上述算法的时间复杂度为 O(log(n))。 在这里一般会省略log2n中的2,这是因为有换底公式的存在,使得底数具体是多少不需要去关心。
平方阶
平方阶一般见于简单的嵌套for循环
例如:
for(int i = 0; i < n: ++i) { for(int j =0; j < n:++j) { /*时间复杂度为O(1)的程序步骤序列*/ } }对于外次for循环的每一次执行,内层for循环都要执行n此,问题的输入规模为n,那么执行的总次数就是n^2,即f(n) = n^2按照大O阶的记法,这个算法的时间复杂度为O(n^2)如果外层的循环变量改为了m。那么外层的输入规模为m,内层的输入规模是n,对外层的每一次执行,内层循环都要执行n此,所以总次数就是n*m因此其时间复杂度为O(m*n)因此,循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。
for(int i =0; i<n; ++i) { for( j = i; j< n; ++j) { /*算法时间复杂度为O(1)的程序步骤序列*/ } } 对这个算法的时间复杂度进行计算, 当i = 0 时,内层循环执行了n次,当i= 1时,内层循环执行了n-1次,当i = 2时内层循循环执行了n-2次……当i等于n-1次时,内层循环执行了1次,那么总的执行次数就是 f(n) = n+ (n-1) + (n-2) + (n-3) +... +1 = n(n+1)/2 = n^2/2 + n/2 那么它的时间复杂度就是O(n^2)
时间复杂度,难点在于对数列的进行的相关运算,所以计算时间复杂度,要强化基础数学能力,尤其是数列方面的知识。
包含方法调用的时间复杂度计算
简单来说,你把函数调用当做宏定义在原地展开,然后按照上面的示例计算即可。也可以利用大O的加法或乘法法则计算,其实都一样
大O的加法法则:f1(n) + f2(n) = O(max(f1(n),f2(n)); f1、f2是为两个关于属于规模和基本运算执行次数的函数,加法运算适用于顺序结构、if结构、switch结构
大O的乘法法则:f1(n)*f2(n) = O(f1(n) * f2(n)) 适用于嵌套的循环,例如上述例子的嵌套for,外层的时间复杂度为O(n),即运算次数和属于规模的函数是f1(n) = n;内层循环的时间复杂度也是O(n),其运算次数和输入规模的函数是f2(n) = n,那么整个嵌套for循环的时间复杂度就是O(n*n)。
简单布尔计算或算数运算以及简单I/O(内存I/O)的时间复杂度是O(1),这个也是大O表示法的单位时间。至于O(1)具体是多长时间,定义这个量值是没有意义的,因为,计算时间复杂度,我们只是希望从理论角度出发,大致的估量它可能消耗的时间,程序的具体运行时间消耗还有赖于具体硬件平台的处理效率。
最坏情况与平均情况
最坏情况运行时间是一种保证那就是运行时间将不会再坏了。在应用中这是一种最重要的需求。
平均运行时间是期望算法的运行时间。
对算法的分析,一种方法是计算所有情况的平均值,这种时间复杂度的计算方法称为平均时间复杂度。另一种方法是计算最坏情况下的时间复杂度。这种方法称为最坏平均时间复杂度、
算法的空间复杂度
算法的空间复杂度:通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n) = O(f(n)),其中n为问题的规模,f(n)为语句关于n所占存储空间的函数。一般情况下,一个程序在机器上执行时,除了需要存储程序本身的指令、常数、变量和输入外,还需要存储对数据操作的存储单元。若输入数据所占空间只取决于问题本身,和算法无关,这样只需要分析该算法在实现时所需的辅助单元即可。若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为O(1)。通常,使用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求。一般说到复杂度,其实都是指“时间复杂度”。
原文地址:https://www.cnblogs.com/ToBeExpert/p/9755905.html