算法效率(时间与空间复杂度)

前言


新的学习阶段又开始了,在更新完C语言后,博主将开始更新数据结构的知识了,说到数据结构想必大家都是知道其重要性吧.

嗯,废话不多说,那我们现在就开始谈谈数据结构吧~


算法效率

什么是算法效率? 即判断一个程序的相对好与坏的方法.算法效率的测评主要有两种:

  • 第一种: 时间复杂度(又称时间效率)
  • 第二种: 空间复杂度(又称空间效率)

而由于现在科技的飞速发展,计算机的存储容量已经极大提高,空间复杂度并不是重点了,我们今天需要着重讲解的就是时间复杂度

时间复杂度


!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!敲黑板!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !! !!! !!!

重要的事情一定要多说几遍,时间复杂度测的

!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!不是时间!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!

!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!不是时间!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!

!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!不是时间!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!

一个程序的执行时间完全会受到很多因素的影响,比如同样的程序,在不同的机器,执行的时间却不一定相同,所以说如果我们要用 时间的长短来评价一个程序的好坏这明显是不科学的,因此才提示时间复杂度的概念 : 算法中的基本操作执行次数,而他的表示方法则是大O渐进表示法,即O(m) ,m是关于次数的表达式


样例1

请问函数Func1的时间复杂度是多少?

void Func1(int N)
{
    int count = 0;
    for (int i = 0; i < N ; ++ i)
    {
        for (int j = 0; j < N ; ++ j)
        {
        ++count;
        }
    }
    for (int k = 0; k < 2 * N ; ++ k)
    {
        ++count;
    }
    int M = 10;
    while (M--)
    {
    	++count;
    }
    printf("%d\n", count);
}

根据程序分析,我们可以得知Func1的基本执行次数是:

F(N) = N² + 2N + 10;

所以用大O渐进表示法就是O(N² + 2N + 10),对吗???,先不回答,我们先看看下面:

NF(N)
1131
10130100
1001021010000
100010020101000000
10000100020010100000000

我们发现,随着N的逐渐增大,F(N)的值也逐渐增大,但是却也与N²的值非常接近,因此我们便可以不看尾数,即我们再用大O渐进表示法时候是不需要精确计算运行次数,而只是求个大概.


  1. **下面是总结的大O表示原则:**

    1. **用常数1取代运行时间中的所有加法常数。**
    2. **在修改后的运行次数函数中,只保留最高阶项。**
    3. **如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶**

所以Func1的时间复杂度是 O(N²),同时提醒一下哦,时间复杂度计算的是在最坏情况下的哦

样例2

void Func2(int N)
{
    int count = 0;
    for (int k = 0; k < 2 * N ; ++ k)
    {
        ++count;
    }
    int M = 10;
    while (M--)
    {
    	++count;
    }
    printf("%d\n", count);
}

经过分析可以得知,Func2的基本执行次数是:

F(N) = 2N + 10;

根据大O表示法原则第一条F(N)变成: F(n) = 2N + 1;

根据大O表示法原则第二条F(N)变成: F(n) = 2N;

根据大O表示法原则第三条F(N)变成: F(n) = N;

所以最终时间复杂度是 : O(N)

样例3

void Func3(int N, int M)
{
    int count = 0;
    for (int k = 0; k < M; ++ k)
    {
    	++count;
    }
    for (int k = 0; k < N ; ++ k)
    {
    	++count;
    }
    printf("%d\n", count);
}

经过分析可以得知,Func3的基本执行次数是:

F(N) = M + N

根据大O渐进表示法原则,可以知道 时间复杂度为: O( M + N)

样例4

void Func4(int N)
{
    int count = 0;
    for (int k = 0; k < 100; ++ k)
    {
    	++count;
    }
    printf("%d\n", count);
}

经过分析可以得知,Func4的基本执行次数是:

F(N) = 100

根据大O渐进表示法原则,可以知道 时间复杂度为: O(1)

样例5

void BubbleSort(int* a, int n)
{
    assert(a);
    for (size_t end = n; end > 0; --end)
    {
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
            if (a[i-1] > a[i])
            {
                Swap(&a[i-1], &a[i]);
                exchange = 1;
            }
    	}
        if (exchange == 0)
            break;
	} 
}

经过分析可以得知,BubbleSort的基本执行次数是:

F(N) = n-1 + n-2 + n-3 + n-4 + … + 3 + 2 + 1;

可以知道那是一个等差数列求和,所以F(N) = n(n-1) / 2 = (1/2)N² - (1/2)N*

按照大O表示法可以知道 时间复杂度为 O(N²)

样例6

int BinarySearch(int* a, int n, int x)
{
    assert(a);
    int begin = 0;
    int end = n-1;
    while (begin < end)
    {
        int mid = begin + ((end-begin)>>1);
        if (a[mid] < x)
        	begin = mid+1;
        else if (a[mid] > x)
        	end = mid;
        else
        	return mid;
    }
    return -1;
}

请看下面随着查找次数与数组长度关系:

查找次数数组长度
1N/2
2N/2/2
3N/2/2/2
x-23
x-12
x1

由此我们可以知道一个关系: 2^x = N,所以 x = ㏒₂(N), 即 F(N) = ㏒₂(N)

所以大O渐进表示法 时间复杂度为 O(㏒₂(N))

递归的时间复杂度求解

递归复杂度的计算公式为: 递归次数 * 每次递归里面的基本操作次数

样例1

long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N-1)*N;
}

递归次数: N

每次递归基本操作次数: 1

**所以F(N) = N*1; **

大O的渐进表示法 时间复杂度为:O(N)

样例2

long long Fibonacci(size_t N)
{
	return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}

递归次数:

image-20210729095524025

所以F(N) = 2^0 + 2^1 + 2^2 + … 2^(n-2)

*这是等比数列求和,所以F(N) = (1-2(n-2)) / (1-2) = 2^(n-2) - 1 = (1/4)2^n - 1

所以大O表示法的 时间复杂度是 O(2^N)****

有人可能会说,这个是不准确的,的确,因为实际上这个次数是小于2^(n-2) - 1的,为什么呢,请看图:

image-20210729100421578

即在中途后,次数就开始下降了,原因是有F(3)以下的数字先达到,就不再需要往下递归了.

但是即使这样,我们想一想按照这样真实的计算,最高次项是多少?? 其实还是2^N,所以时间复杂度仍然为O(2^N)

空间复杂度


!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!敲重点!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!

!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! 空间复杂度 !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!

计算的不是内存大小,而是程序每次执行,说临时开辟的变量数量

并且时间复杂度是累积的,但是空间复杂度不累计,并且空间复杂度的表示也是大O表示


样例1

void BubbleSort(int* a, int n)
{
    assert(a);
    for (size_t end = n; end > 0; --end)
    {
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
            if (a[i-1] > a[i])
            {
                Swap(&a[i-1], &a[i]);
                exchange = 1;
            }
        }
        if (exchange == 0)
        break;
    }
}

通过观察可以看见,程序开辟的变量数量只有3个,即常数个,所以 空间复杂度为 O(1)

样例2

long long* Fibonacci(size_t n)
{
    if(n==0)
    	return NULL;
    long long * fibArray =(long long *)malloc((n+1) * sizeof(long long));
    
    fibArray[0] = 0,fibArray[1] = 1;
    for (int i = 2; i <= n ; ++i)
    {
    	fibArray[i ] = fibArray[ i - 1] + fibArray [i - 2];
    }
    return fibArray ;
}

这个程序可以看见动态开辟了n+1个空间,所以空间复杂度为O(N)

样例3

long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N-1)*N;
}

由于是递归,递归的空间复杂度计算类似. 等于递归开辟的栈空间*每次递归开辟的变量数量

此题每次递归没有开辟变量,所以只计算递归次数,可知道连续递归了N次,所以O(N)

样例4

long long Fibonacci(size_t N)
{
	return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}

image-20210729100421578

还记得我们说过的,空间复杂度不累计吗???

这个题做多连续递归多少次? 答案n-1,

因为要想知道F(N)必先知道F(N-1),要想知道F(N-1)必先知道F(N-2)…依次往下.

每次递归时候并没有开辟变量数量,所以空间复杂度为O(n)

  • 48
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 29
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

捕获一只小肚皮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值