点进来你就是我的人了
博主主页:戳一戳,欢迎大佬指点!
目录
前言
1.算法效率
事后统计法
事前分析估算的方法
2.时间复杂度
常数阶O(1)
线性阶O(n)
对数阶O(logN)
线性对数阶O(nlogN)
平方阶O(n2)
指数阶O(2^N)
3.空间复杂度
随着近些年计算机快速的发展,对程序员来说入职要求也越来越高了,公司对我们的代码能力要求也越来越高了,大厂笔试中几乎全是算法题而且难度大,中小厂的笔试中也会有算法题,因此,作为程序员,掌握一些比较高级的算法尤为重要.提到算法我们经常会听见时间和空间比较复杂,那么这个时间和空间是什么意思呢?
如何去衡量一个算法的好坏?
通常我们从时间效率和空间效率两个方面去分析算法的好坏。时间效率即时间复杂度,空间效率被称为空间复杂度。时间复杂度主要是衡量一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间。
常见的复杂度大小比较:O(2^N) > O(N^2) > O(N*logN) > O(N) > O(logN) > O(1)
这种方法可行,但不是一个好的方法。该方法有两个缺陷:一是要想对设计的算法的运行性能进行评测,必须先依据算法编制相应的程序并实际运行;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优势。
因事后统计方法更多的依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优劣。因此人们常常采用事前分析估算的方法。
在编写程序前,依据统计方法对算法进行估算。一个程序在计算机上运行时所消耗的时间取决于下列因素:
(1) 算法采用的策略、方法;(2).编译产生的代码质量;(3) 问题的输入规模;(4)机器执行指令的速度。
定义:在计算机科学中,算法的时间复杂度是一个数学函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间与其中语句的执行次数成正比例,算法中基本的执行次数,即算法的时间复杂度。
通常在计算算法的时间复杂度时,并不一定要计算精确的执行次数,而只需要计算大概执行次数,我们通常使用大O渐进法来表示。
1.用常数1来取代运行时间中所有的加法常数;
2.只保留最高的阶项;
3.如果最高项存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶.
代码演示如下:
//请计算一下func1基本操作运行了多少次
void func1(int N){
int count = 0;
for (int i = 0; i < N ; i++) {
for (int j = 0; j < N ; j++) {
count++;
}
}//这个for循环运行 N * N 次
for (int k = 0; k < 2 * N ; k++) {
count++;
}//这个for循环运行 N 次
int M = 10;
while ((M--) > 0) {
count++;
}//这个操作运行 10 次
System.out.println(count);
}
对于上述代码进行时间复杂度分析:
第一个嵌套for循环的执行次数为N^2,第二个for循环的执行次数为2N,第三个while循环的执行次数是10,则F(N)=F(N^2) + F(2N)+10,根据大O渐进表示法,该算法的时间复杂度为O(N^2).
算法的时间复杂度存在最好、平均、最坏情况:
最好情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最坏情况:任意输入规模的最小运行次数(下界)
注意:O(1)表示基本语句的执行次数是一个常数,一般来说,只要算法中不存在循环语句,时间复杂度就为O(1)。
下面选取一些常见的进行讲解
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如:
int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。
这个在最开始的代码示例中就讲解过了,如:
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。
int i = 1;
while(i
从上面代码可以看到,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n
也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)
线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。
就拿上面的代码加一点修改来举例:
for(m=1; m
平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
举例:
for(x=1; i<=n; x++)
{
for(i=1; i<=n; i++)
{
j = i;
j++;
}
}
这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n*n),即 O(n²)
如果将其中一层循环的n改成m,即:
for(x=1; i<=m; x++)
{
for(i=1; i<=n; i++)
{
j = i;
j++;
}
}
那它的时间复杂度就变成了 O(m*n)
立方阶O(n³)、K次方阶O(n^k)
参考上面的O(n²) 去理解就好了,O(n³)相当于三层n循环,其它的类似。
递归的时间复杂度计算:递归的次数*每次递归后代码的执行次数(也是用大O渐进法表示)
int Fibonacci(int N)
{
return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}
上面的代码是一个计算斐波那契数列的方法,使用的是递归的方法,我们知道递归的方法来计算斐波那契数列是非常低效的,最好还是使用循环的方法,但是递归的时间复杂度是多少呢?
我们知道每一次调用这个方法时,我们的时间复杂度是一个常数,那么这个递归的时间复杂度就是我们一共调用了多少次方法,现在我们来分析一下我们到底调用了多少次这个方法。
其调用结构如图所示
当我们输入N时,方法会进行两调用,然后不断地调用,其调用的结果如图所示,在右下角是有一个空缺区域的,但是我们可以把这一块空缺的区域看作一个常数,假设它是满的,那么我们执行调用的总次数为F(N)=2⁰+2¹+2²+…+2^N-1=2^N-1(使用等比数列得出结果),所以该算法的时间按复杂度为O(2^N)。
由以上的计算我们就可以发现用递归来算斐波那契数的算法的时间复杂度太高了,也就说明了这个算法的低效。
除此之外,其实还有 平均时间复杂度、均摊时间复杂度、最坏时间复杂度、最好时间复杂度 的分析方法,有点复杂,这里就不展开了。
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数,其计算规则与时间复杂度类似,也采用大O渐进表示法.
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
1.一个算法在计算机上占用的内存包括:程序代码所占用的空间,输入输出数据所占用的空间,辅助变量所占用的空间这三个方面,程序代码所占用的空间取决于算法本身的长短,输入输出数据所占用的空间取决于要解决的问题,是通过参数表调用函数传递而来,只有辅助变量是算法运行过程中临时占用的存储空间,与空间复杂度相关;
2.通常来说,只要算法不涉及到动态分配的空间,以及递归、栈所需的空间,空间复杂度通常为O(1);
代码演示如下:
void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {
boolean sorted = true;
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}
if (sorted == true) {
break;
}
空间复杂度:O(1)
这里需要理解:算法在运行过程中临时占用存储空间(额外)大小的量度,这里这开辟end,sorted ,i 三个零时变量,因为大O渐进表示法,3为常数所以为1 。
空间复杂度O(n)
int[] m = new int[n]
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 O(n)
谢谢你这么好看还能看到最后!我们下期见