Talk is cheap, show me the code.
文章目录
今天简单了解算法的概念以及好算法的特质,并学会求解算法的时间复杂度与空间复杂度,本文会给出大量的例子来说明,话不多说,一起来学习把!
一、算法的基本概念
1.什么是算法?
算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令代表一个或多个操作。
有人说:程序=数据结构+算法
其实,数据结构是要处理的信息,算法是处理信息的步骤。
2.算法的五个特性
①.有穷性:在有限的时间内能执行完。
注意:算法是有穷的,而程序可以是无穷的。例如网站和app等,你使用的时候它是在有限时间内完成指令的,但是它是一直在运行的,也就是说程序是不停止的。
②确定性:相同的输入只会产生相同输出。
③可行性:可以用已有的基本操作来实现
④输入:一个算法可以有零个或多个输入,可以输入数据。
⑤输出:一个算法可以有有一个或多个输出,可以输出结果。
3.好算法的特质
①正确性:算法能正确的解决问题。
②可读性:利于让别的人也看得懂。
③健壮性:当输入非法数据时,能发现并进行处理。
④高效率与低存储量需求:即算法省时省内存(其实为下文中的时间复杂度和空间复杂度低)。
二、算法的时间复杂度
1.算法的执行时间可以表示算法效率大小吗?
度量一个算法的执行时间通常有①事后统计②事前分析估算 两种方法,而不可能每一个问题我们都先去写出一种算法再事后统计它所用的时间,这不实际。而事前分析估算的话,程序在计算机上运行时间取决于以下几个因素:
①算法选用何种策略;
②问题的规模,如100个数求和与100000个数求和;
③写代码的语言,语言级别越高,执行效率越低;
④编译程序所产生的机器代码的质量;
⑤机器执行指令的速度。
显然,由于上述原因,可能你换个电脑,换个语言,甚至换个编译器,代码执行的时间都会不同,所以以代码执行时间的长短来衡量算法的效率是不合适的。
2.算法的时间复杂度
一个算法是由控制结构和原操作构成的,一般来说,从算法中选取一种基本操作,以该基本操作执行的次数作为算法的时间复杂度。例如:
int n; //①
for (int i = 0; i < n; i++) //②
{
printf("text\n"); //③
}
对于上述程序来说:①执行了1次;②执行了n+1次;③执行了n次。总共为2n+2次。
一个算法中语句执行的次数成为时间频度,记为T(n),上例T(n)=2n+2.
3.大O表示法
①大O表示法
一般时间复杂度是问题规模n的某个函数T(n),由于当n足够大时,2n+2与n的区别不大,他们是同一数量级的,因此记作上例的时间复杂度T(n)=O(2n+2)=O(n),这种表示法称作大O表示法。
②大O阶推导
1.用常数1取代运行时间中的所有加法常数(假设基本语句的执行次数为一确定常数)
2.在修改后的运行次数函数中,只保留最高阶
3.如最高阶不是常数,则去除最高阶的系数
③例:
T(n)=O(2n+2)=O(n)
T(n)=O()=O(
)
T(n)=O(4+
)=O(
)
T(n)=O(n!+8)=O(n!)
······
④常见复杂度的对比
一般常见复杂度有:
123456 | O(1) | 常数阶 |
2n+8 | O(n) | 线性阶 |
O(![]() |
立方阶 | |
5nlog2n+6 | O(nlog2n) | 对数阶 |
O(![]() |
指数阶 | |
n! | O(n!) | 阶乘 |
常见复杂度比较:“常对幂指阶”
O(1)
) ) ) )
如图可帮助记忆:
4.三种复杂度
一般有最好、最坏、平均三种复杂度。举个例子:
从一个长度为n的数组中找到值为n的那个元素
void fun(int arr[], int n)//n为问题规模
{
for (int i = 0; i < n; i++)
{
if (arr[i] == n)
{
printf("我找到啦!!!\n");
break; //找到后立即跳出循环
}
}
①最好时间复杂度: 当arr[0]=n时,T(n)=O(1);
②最差时间复杂度:当arr[n]=n时,T(n)=O(n);
③平均时间复杂度:假设元素n在每一个位置的概率都为1/n,T(n)=O((1+2+3+···+n)/n )=O(n).
一般情况下,我们关注最差和平均时间复杂度。
5.举例计算
在计算时,我们只关注循环部分即可
int Time(int n)
{
int count = 0;
int x = 2;
while (x < n / 2)
{
x *= 2;
count++;
}
return count;
}
①上例当x
故T(n)=O(log2n),其他求法类似。
三、算法的空间复杂度
1.空间复杂度的概念
空间复杂度为算法所耗费的存储空间,它也是问题规模n的函数。用S(n)表示。
2.大O表示法
同上述时间复杂度的大O表示法,只不过程序执行的次数变成了变量存储空间的多少。
例如:
int i = 1;
int j = 0;
i++;
j = 5;
int m = i;
上例定义了i,j, m三个整型变量,故S(n)=O(3*4)=O(1).
值得注意的是,如果算法所需要的存储空间为常量,我们一般称此算法为原地工作。
3.递归调用的 空间复杂度
对于递归调用问题,一般来说,其空间复杂度=递归调用的深度。什么意思呢?下面以一个例子加以说明:
void fun(int n)
{
int a, b, c;
if (n > 1)
{
fun(n - 1);
printf("第%d次测试\n", n - 1);
}
}
递归调用的深度为n,故S(n)=O(n)
实际上,在fun函数中我们定义了n,a,b,c四个个整型变量,再在每一次调用fun函数的时候都会重新分配内存,故最后所用存储空间为4*4B*n. S(n)=O(n).
再看一个例子:
void fun(int n)
{
int a, b, c;
int arr[n];
if (n > 1)
{
fun(n - 1);
printf("第%d次测试\n", n - 1);
}
}
我们仅仅只是新定义了一个数组arr. 每次都会定义一个数组和四个整型变量a,b,c,n.故所用空间为(16n+4*(1+2+···+n))B, 由大O表示法知S(n)=O()。
时间复杂度关注循环部分,递归调用的空间复杂度等于递归调用的深度最后,有错误之处,希望大家指正,欢迎大家关注,一起交流学习呀