一、算法的复杂度
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容呈很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
二、时间复杂度
时间复杂度的定义:在计算机科学中,(算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算田来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。
大O表示法:算法的时间复杂度通常用大O符号表述,定义为T[n] = O(f(n))。称函数T(n)以f(n)为界或者称T(n)受限于f(n)。 如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n)。T(n)称为这一算法的“时间复杂度”。当输入量n逐渐加大时,时间复杂度的极限情形称为算法的“渐近时间复杂度”。
推导大O阶方法:
1.用常数1取代运行时间中所有的加法常数
2.在修改后的运行次数函数中,只保留最高阶项,其他项对结果的影响不大
3.如果最高阶项存在,且不是1,则去除与这个项相乘的常数,也就是不要系数
算法的时间复杂度存在最好、平均和最坏情况,这里用在长度为N的数组中搜索x举例
最好情况:任意输入规模的最少运行次数(上界),1次找到
平均情况:任意输入规模的期望运行次数,2/N次找到
最坏情况:任意输入规模的最大运行次数(下界),N次找到
假设数组为acdhegfs。如果逐个遍历,查找a一次就能找到,查找h四次就能找到,查找s八次才能找到;
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
void Swap(int* a,int* b)
{
int c=*a;
*a=*b;
*b=c;
}
int* sortArray(int* nums, int numsSize, int* returnSize)
{
for(int i=0;i<numsSize;i++)
{
int if_finish=0;
for(int j=1;j<numsSize-i;j++)
{
if(nums[j-1]>nums[j])
{
Swap(&nums[j-1],&nums[j]);
if_finish=1;
}
}
if(if_finish==0)
{
break;
}
}
*returnSize=numsSize;
return nums;
}
对于如上的冒泡排序,第一个元素要对比N-1次,第二个元素对比N-2次,以此类推,第k个元素对比N-k次,最后一个元素对比1次。将所有的对比次数求和,等差数列求和为N*(N-1)/2,所以时间复杂度为O(N^2)
如上图所示的二分查找法,对有序的数组进行查找,假设数组是递增的,每次查找,查找区间缩小一半,直到最后只剩一个数字,也就是查找的数字。假设查找m次,那么N=1*2*2*2*2……,N=2^m次,m=log2(N),2为底数
递归时间复杂度:每次递归调用执行次数累加
如上图所示的阶乘, Fac(N)+Fac(N-1)+Fac(N-2)+……+Fac(1),总共是N个递归函数调用,所以时间复杂度为O(N)
如上图所示的阶乘内部带循环, Fac(N)+Fac(N-1)+Fac(N-2)+……+Fac(1),总共是N个递归函数调用,第一次调用内部循环N次,第二次调用内部循环N-1次,第k次调用内部循环N-k次,第N次调用内部循环1次。所以是等差数列求和,N+(N-1)+(N-2)+(N-3)+……+1。也就是N*(N-1)/2。时间复杂度为O(N^2)
如上图所示的斐波那契数列 ,总共调用了2^0+2^1+2^2+……+2^(N-2)=2^(N-1)-1,等比数列求和,所以时间复杂度为O(2^N)
三、空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
空间复杂度为O(1)
冒泡排序的空间复杂度为O(1)
阶乘的空间复杂度为O(N),因为每次调用都会开辟一块新的空间, Fac(N)+Fac(N-1)+Fac(N-2)+……+Fac(1),总共是N个递归函数调用,所以空间复杂度为O(N)
如上图所示的斐波那契数列,用malloc开辟了n+1块大小为sizeof(long long)的空间,所以空间复杂度为O(N)
递归的斐波那契数列,由于函数调用的栈帧不能无限创建,如果N过大程序会报错。空间可以重复利用,所以总共开辟了Fib(N)+Fib(N-1)+Fib(N-2)+……+Fib(1),总共开辟了N个空间,所以空间复杂度为O(N)