有关算法时间耗费分析,我们称为算法的时间复杂度分析,有关算法的空间耗时分析,我们称为算法的空间复杂度分析。
算法的时间复杂度分析:
其实就是分析某个程序在运行的过程中使用的时间
事前分析估算法:
在计算机程序编写前,依据统计方法对算法进行估算,经过总结,我们发现一个高级语言编写的程序在计算机上运行消耗的时间取决于下列因素:
1、算法采用的策略和方案;
2、便意产生的代码质量
3、问题的输入规模(所谓的问题输入规模就是输入量的多少)
4、机器执行指令的速度
做算法分析最重要的是把核心操作的次数和输入规模关联起来。
函数渐近增长
给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么我们说f(n)的增长渐近快于g(n)。
规则:
1、算法函数中的常数可以忽略;
2、算法函数中最高次幂的常数因子可以忽略
3、算法函数中最高次幂越小,算法效率越高
大O记法
定义:
在进行算法分析时,语句总的执行次数T(n)是关于问题规模的n的函数,进而分析T(n)随着n的变化情况并确定T(n)的量级,算法的时间复杂度,就是算法时间量度,记作:T(n)=O(f(n))。它表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,筒称时间复杂度,其中f(n)是问题规模n的某个函数。
其实就是:执行次数=执行时间
规则:
1、用常数1取代运行时间中的所有加法常数
2.在修改后的运行次数中,只保留高阶项
3.如果最高阶项存在,且常数因子不为1,则去除与这个项相乘的常数
算法中常见的大O阶
1.线性阶
一般含有非嵌套循环涉及线性阶,线性阶就是随着输入规模的扩大,对应计算次数呈直线增长,例如
public static void main(String[] args){ int sum = 0; int n=100; for(int i=1;i<=n;i++){ sum+=i; } System.out.println("sum="+sum); }
它的循环时间复杂度为O(n),因为循环体中的代码需要执行n次。
2、平方阶
一般嵌套循环属于这种时间复杂度
public static void main(String[] args){ int sum=0,n=100; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ sum+=i; } } System.out.println(sum); }
n=100,也就是说外层循环每执行一次,内层循环就执行100次,那总共程序想要从这两个循环中出来,就需要执行100*100次,也就是说n的平方次,所以这段代码的时间复杂度是O(n^2)
3、立方阶
一般三层嵌套循环属于这种时间复杂度
public static void main(String[] args){ int x=0,n=100; for(int i=1;i<=n;i++){ for(int j=1;i<=n;j++){ for(int j=i;i<=n;j++){ x++; } } } System.out.println(x); }
n=100,也就是说,外层循环每执行一次,中间循环就执行100次,中间循环每执行一次,内层循环需要执行100次,那总共程序想要从这三个循环中出来,就需要执行100100100次,也就是n的立方,所以这段代码的时间复杂度时O(n^3)
4、对数阶
对数,属于高中数学的内容,我们分析程序以程序为主,数学为辅,所以不用过分担心。
int i=1,n=100; while(i<n){ i=i*2; }
由于每次i*2之后,就距离n更进一步,假设有x个2相乘后大于n,则会退出循环,由于是2^x=n,所以这个循环的时间复杂度为O(logn);
对于对数阶,由于随着输入规模n的增大,不管底数为多少,他们的增长趋势是一样的,所以我们会忽略底数。
5、常数阶
一般不涉及循环操作的都是常数阶,因为它不会随着n的增长二增加操作次数。
public static void main(String[] args){ int n=10; int i=n+2; System.out.println(i); }
不管规模n是多少,都执行2次,根据大O推导法则,常数用1来代替,所以上述代码的时间复杂度为O(1)
时间复杂度总结:
描述 | 增长的数量级 | 说明 | 举例 |
---|---|---|---|
常数级别 | 1 | 普通语句 | 将两个数相加 |
对数级别 | logN | 二分策略 | 二分查找 |
线性级别 | N | 循环 | 找出最大元素 |
线型对数级别 | NlogN | 分治思想 | 归并排序 |
平方级别 | N^2 | 双层循环 | 检查所有元素对 |
立方级别 | N^3 | 三层循环 | 检查所有三元组 |
指数级别 | 2^N | 穷举查找 | 检查所有子集 |
他们的复杂程度从低到高依次为:
O(1)->O(logn)->O(nlogn)->O(n^2)->O(n^3)
根据前面我们会发现,从平方阶开始,随着输入规模的增大,时间成本会急刚增大,所以,我们的算法,尽可能的追 求的是O(1),O(n),O(nlogn)这几种时间复杂度,而如果发现算法的时间复杂度为平方阶、立方阶或者更复杂的,那我们可以分 为这种算法是不可取的,需要优化。
函数调用的时间复杂度分析
案例一、
public static void main(String[] args){ int n=100; for(int i=0;i<n;i++){ show(i); } } private static void show(int i){ System.out.println(i); }
在mal方法中,有一个for循环,篇环体调用了show方法,由于show方法内部只执行了一行代码,所以show方法的时间复杂度为 O(1)那man方法的加时间复杂度就是O(n)
案例二、
public static void main(String[] args){ int n=100; for(int i=0;i<n;i++){ show(i); } } private static void show(int i){ for(int j=0;j<i;j++){ System.out.println(i); } }
在mai方法中,有一个for循环,循环体调用了show方法,由于show方法内部也有一个for循环,所以show方法的时间复杂度为O(n),那main方法的时间复杂度为O(n^2)
案例三、
public static void main(String[] args){ int n=100; show(n); for(int i=0;i<n;i++){ show(i); } for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ System.out.println(j); } } } private static void show(int i){ for(int j=0;j<i;j++){ System.out.println(i); } }
在show方法中,有一个for活环,所以show方法的时间复杂度为O(n),在main方法中,show(n)这行代码内部执行的次数为n,第一个for循环内调用了show方法,所以其执行次数为n^2,第二个嵌套for循环内只执行了一行代码,所以其执行次数为n^2,那么main方法总执行次数为n+n^2+n^2=2n^2+n,根据大O推导规呗则,去掉n保留最高阶项,并去掉最高阶项的常数因子2,所以最终main方法的时间 复杂度为O(n^2)
最坏情况
public int search(int num){ int[] arr={11,10,8,97,22,23,0}; for(int i=0;i<arr.length;i++){ if(num==arr){ return i; } } return -1; }
最好情况:
查找的第一个数字就层期望的数字,那么算法的细时间复杂度为O(1)
最坏情况:
查找的最后一个数字,才是期望的数字,那么算法的时间复杂度为O(n)
平均情况:
任何数字查规的平均成本是O(n/2)
最坏情况是一种保证,在应用中,这是一种最基本的保障,即使在最坏情况下,也能够正常提供服务,所以,除非特别指定,我们提 到的运行时间都指的是最坏情况下的运行时间。