1、复杂度分析(学习笔记)

1、复杂度分析(学习笔记)

1. 为什么需要复杂度分析

​ 统计代码的执行时间和占用的内存,完全可以通过统计、监控来得到,为什么还要做时间、空间复杂度分析呢?

其实,这种统计方法在一些算法书籍中叫做事后统计法。但是这种方法的局限性很多。

  1. 在实际测试环境中,硬件的不同对测试结果有很大的影响。

  2. 测试数据受数据规模的影响。

时间、空间复杂度分析的作用就是不用具体的测试数据,就可以粗略的估计算法的执行效率的方法。

2. 大O复杂度表示法

算法的执行效率,粗略的讲,就是算法代码执行的时间。

下面是一段简单的C语言代码

int cal(int n)//求阶乘
{
    int result=1;
    for(int i=1;i<=n;i++)
    {
        result *= i;
    }
    return result;
}

我们假设处理器执行每行代码的时间为unitTime,第3、8行执行了一次,第4、6行执行了n次,那么这个函数总执行时间为
T ( n ) = ( 2 n + 1 ) × u n i t T i m e T(n) = (2n+1)×unitTime T(n)=(2n+1)×unitTime
代码的执行时间T(n)与代码执行次数成正比。

那么下一段代码:

int cal(int n)
{
    int sum=0;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            sum+=i*j;
        }
    }
    return sum;
}

那么T(n)
T ( n ) = ( 2 n 2 + 2 n + 1 ) × u n i t T i m e T(n) = (2n^2+2n+1)×unitTime T(n)=(2n2+2n+1)×unitTime


由以上两个例子可以得出,代码执行时间T(n)与代码执行次数n成正比。所以
T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n))
大O时间复杂度实际上并不表示代码的执行时间,而是表示代码执行时间随数据规模的增长的变化趋势,所以叫做渐进时间复杂度(asymptotic time complexity),简称时间复杂度。

当n很大时,公式中的低阶、常量、系数三部分并不改变增长的趋势,所以可以忽略。只需要记录最大量级就可以了。所以刚才的例子可以记为
1 、 T ( n ) = O ( n ) 1、T(n)=O(n) 1T(n)=O(n)

2. 、 T ( n ) = O ( n 2 ) 2.、T(n)=O(n^2) 2.T(n)=O(n2)

3. 时间复杂度分析
  1. 只关注循环代码执行次数最多的一段代码
  2. 加法法则:总复杂度等于量级复杂度最大的那段代码的复杂度
  3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码都复杂度的乘积
4. 几种常见时间复杂度实例分析
常量阶O(1)
对数阶O(logn)
线性阶O(n)
线性对数阶O(nlogn)
k次方阶O(n^k)
指数阶O(2ⁿ)
阶乘阶O(n!)

可分为两类:

  1. 多项式量级

  2. 非多项式量级:O(2ⁿ) ,O(n!)

    时间复杂度为非多项式量级的算法问题叫做NP(Non-Deterministic Polynomial,非确定多项式)问题

下面为举例

//O(1)
int a=5,c=9;
int result = a+c;
//O(logn)
int k=1;
while(k<n)
{
    k*=2;
}

2 x = n , 那 么 x = l o g 2 n 2^x=n,那么 x=log_2{n} 2x=n,x=log2n

不管以哪个数作为对数的底数,都以O(logn)来标志,对数的底数可以相互转换例如
l o g 2 n = l o g 3 n / l o g 3 2 log_2{n}=log_3{n}/log_3{2} log2n=log3n/log32
那么:
l o g 3 n = l o g 3 2 × l o g 2 n log_3{n}=log_3{2}×log_2{n} log3n=log32×log2n

空间复杂度分析

前面说过时间复杂度全称为渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。那么类比一下,空间复杂度全称为渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系

例如:

void demo(int n)
{
    int *p = (int*)malloc(sizeof(int)*n);
    for(int i=0;i<n;i++)
    {
        p[i]=i+1;
        printf("%d ",p[i]);
    }
}

第三行申请了一个大小为n的int类型的数组,空间复杂度为O(n)。

空间复杂度为O(logn)例子

以二进制形式存储一个十进制数n,所占bit与n的关系

O ( l o g n ) b i t O(logn) bit O(logn)bit

5. 时间复杂度分析++
  1. 最好情况时间复杂度
  2. 最坏情况时间复杂度
  3. 平均情况时间复杂度
  4. 均摊时间复杂度

下面一个函数 ,查找数组中的值的索引

int Fine(vector<int> arr,int value)
{
    int pos=-1;
    for(int i=0;i<arr.size();i++)
    {
        if(arr[i] == value)
        {
            pos=i;
            break;
        }
        return pos;//没找到,返回-1
    }
}

在这段代码中,用上面的分析方法并不适用,因为查找的值的不同,循环的次数也是不一样的。

那么引入三个概念:最好情况时间复杂度、最坏情况时间复杂度、平均情况时间复杂度。

如果要查找的value就在数组中的第一个位置,则此种情况为最好的情况,时间复杂度为O(1)。

如果要查找的value不在数组中,则此种情况为最坏的情况,时间复杂度为O(n)。

平均情况时间复杂度,根据概率论的知识,为了方便理解,假设value在数组内和不在数组内的概率相等都为0.5,要查找的数据出现在0~n-1的位置的概率也是一样的,为1/n。所以,要查找的value出现在数组任意位置的概率为1/(2n)。那么平均时间:
1 × ( 1 / 2 n ) + 2 × ( 1 / 2 n ) + 3 × ( 1 / 2 n ) + . . . + n × ( 1 / 2 n ) + n × ( 1 / 2 ) = ( 3 n + 1 ) / 4 1×(1/2n)+2×(1/2n)+3×(1/2n)+...+n×(1/2n)+n×(1/2)=(3n+1)/4 1×(1/2n)+2×(1/2n)+3×(1/2n)+...+n×(1/2n)+n×(1/2)=(3n+1)/4
(3n+1)/4就是加权平均值,也就是期望值,那么用大O表示法为O(n)。

均摊复杂度

先看一段代码

vector<int> arr(n); //长度为n的数组
int count=0;
void push(int val)
{
    if(count==arr.size())
    {
        for(int i=1;i<arr.size();i++)
        {
            arr[0]+=arr[i];
            arr[i]=0;
            count=1;
        }
    }
    arr[count++]=val;
}

该段代码,在push函数中,向数组arr中添加数据,如果数组不满,直接放在尾部,如果数组满了,将所有元素的和放在第一个位置,清空其他位置的数据。

该函数出现最坏情况的频率时有规律的,每n-1次push数据,下一次会出现最坏情况。也就是每n-1次O(1)都会紧跟着一次O(n)。

针对这种情况,有一种简单的分析方法:摊还分析法,通过摊还分析法得到的时间复杂度叫做均摊时间复杂度

如何计算均摊复杂度?

每n-1次O(1)都会紧跟着一次O(n),把耗时多的那一次操作均摊到前n-1次中去,那么该n次操作的均摊时间复杂度为O(1)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值