算法的效率
即是算法的复杂度
,包括时间
和空间
两方面
1.时间复杂度
如果一个问题的规模是n
,解决这一问题的某一算法的时间为T(n),它是n
的某一函数 ,**T(n)**称为这一算法时间复杂度
。
大O记法
:在这种描述中使用的基本参数是n,即问题实例的规模,把复杂性或运行时间表达为n的函数,这里的"O"表示量级(Order),它允许使用"=“代替"≈”。如n2+2n+1=O(n2),表示n足够大时,左边约等于n2。
如何推到大O?
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数。
- 常数阶
int sum = 0, n = 100;
sum = (1+n)*n/2;
T(n) = O(1) 改代码运行次数与n无关,常数
- 线性阶:一般含有非嵌套循环涉及线性阶,线性阶就是随着问题规模n的扩大,对应计算次数呈直线增长。
int i , n = 100, sum = 0;
for( i=0; i < n; i++ )
{
sum = sum + i;
}
T(n) = O(n) 循环体中的代码需要被执行n次
- 方阶
int i, j, n = 100;
for( i=0; i < n; i++ )
{
for( j=0; j < n; j++ )
{
System.out.println(i,j);
}
}
T(n) = O(n2) 外层执行1次,内层循环100次,共执行1002。
int i, j, n = 100;
for( i=0; i < n; i++ )
{
for( j=i; j < n; j++ )
{
System.out.println(i,j);
}
}
n=0,内层循环执行n次,n=1时,内层循环执行n-1次…,i=n-1时,内层循环执行1次
T(n)=n+(n-1)+(n-2)+····+2+1 =(n+1)*n/2=n2/2+n/2 = O(n2)
- 对数阶
int i = 1, n = 100;
while( i < n )
{
i = i * 2;
}
2*2···22=2x>n ⇒
T(n)=x=logn=O(logn)
- 表格展示
例子 | 时间复杂度 | 术语 |
---|---|---|
456415 | O(1) | 常数阶 |
4n+6 | O(n) | 线性阶 |
4n2+3n+1 | O(n2) | 平方阶 |
4log(2)n+6 | O(logn) | 对数阶 |
3n+ 4nlog(2)n+6 | O(nlogn) | nlog^n阶 |
4n3+3n | O(n3) | 立方阶 |
2n | O(2n) | 指数阶 |
- 最坏情况与平均情况
我们查找一个有n个随机数字数组中的某个数字,最好的情况
是第一个数字就是,那么算法的时间复杂度为O(1)
,但也有可能这个数字就在最后一个位置,那么时间复杂度为O(n)
。
平均运行时间
是期望的运行时间。
最坏运行时间是一种保证。在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间
。
2.空间复杂度
算法的空间复杂度
:算法在执行过程所占用的存储空间大小,用S(n)
表示。
我们在写代码时,完全可以用空间
来换去时间
。
举个例子说,要判断某年是不是闰年,你可能会花一点心思来写一个算法,每给一个年份,就可以通过这个算法计算得到是否闰年的结果。
另外一种方法是,事先建立一个有2050个元素的数组,然后把所有的年份按下标的数字对应,如果是闰年,则此数组元素的值是1,如果不是元素的值则为0。这样,所谓的判断某一年是否为闰年就变成了查找这个数组某一个元素的值的问题。
第一种方法相比起第二种来说很明显非常节省空间,但每一次查询都需要经过一系列的计算才能知道是否为闰年。第二种方法虽然需要在内存里存储2050个元素的数组,但是每次查询只需要一次索引判断即可。
这就是通过一笔空间上的开销来换取计算时间开销的小技巧。到底哪一种方法好?其实还是要看你用在什么地方。
定义:算法的空间复杂度通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式记作:S(n)=O(g(n)),其中,n为问题的规模,g(n)为语句关于n所占存储空间的函数。
通常,我们都是用“时间复杂度”来指运行时间的需求,是用“空间复杂度”指空间需求。
当直接要让我们求“复杂度”时,通常指的是时间复杂度。
显然对时间复杂度的追求更是属于算法的潮流!
参考地址链接:https://www.jianshu.com/p/88a1c8ed6254