3.算法的复杂度分析

本文深入探讨了算法的基本特征,包括有穷性、确切性、输入、输出和可行性,并阐述了数据结构与算法的关系。文章通过实例详细解释了时间复杂度和空间复杂度的概念,以及如何计算和分析它们。同时,通过优化代码展示如何减少时间复杂度和空间复杂度,以提高程序效率。最后,文章提到了在实际编程中如何根据应用场景权衡时间和空间复杂度,并介绍了记忆化技术作为优化手段。
摘要由CSDN通过智能技术生成

算法

**算法(Algorithm)**是对某一个或者某一类问题的解决方案的描述,根据问题的输入,在有限的计算时间里输出预期的结果。不同的算法解决问题所需的时间和空间可能会不同,通常用时间复杂度和空间复杂度来评估算法的优劣。

算法的特征
  1. 有穷性。算法必须在执行有限个操作后终止。
  2. 确切性。算法的每一个操作必须有明确的定义。
  3. 输入项。算法有零个或多个输入,描述算法的初始状态。
  4. 输出项。算法有一个或多个输出,没有输出的算法我们认为是没有意义的。
  5. 可行性。算法的每个计算操作都可以在有限时间内完成。

数据结构描述了数据元素之间的逻辑关系,算法描述了数据元素的操作步骤,数据结构和算法组成了程序世界。数据结构和算法之间是不可分割的关系,数据结构是程序的基础,算法将数据互相联系起来,形成了一套能解决具体问题的方案。

在解决问题时,一般我们会优先确定数据结构,然后再来完善算法,有时也会反过来,根据算法来选择合适的数据结构。选择一个合适的数据结构,可以降低算法的复杂度,提高算法的效率。

复杂度分析

算法的复杂度分为算法的时间复杂度空间复杂度

如果有一个辅助函数f(n),在n趋向于无穷大时,T(n)/f(n)的极限值为不等于0的常数,则我们近似的讲f(n)替代T(n),记为T(n)=O(f(n)),称为算法的渐进时间复杂度

时间复杂度只关心算法中最耗时的部分,舍去常数部分,通常用简单的函数来表示。例如,某算法计算出来 T(n) = 2n3+4n2+n,则它的时间复杂度为O(n3)。按效率从高到低排列,时间复杂度一般有以下几种。

  • 常数阶 O(1)
  • 对数阶 O(log n)
  • 线性阶 O(n log n)
  • 平方阶 O(n2)
  • 立方阶 O(n3)
  • 指数阶 O(2n)
  • 阶乘阶 O(n!)

算法的空间复杂度是指运行该算法所占用的存储空间大小,记为 S(n),和时间复杂度类似,通常也是取它的渐进空间复杂度,用一个直观的函数来表示。通过空间复杂度,我们可以预估出算法运行所需的存储空间,包括指令空间、数据空间、动态申请的内存空间等。

例题

举个例子来描述下算法时间复杂度的计算过程。欧拉计划第一题3或5的倍数现有如下代码,可以计算处语句1执行1次,语句2执行了n次,则T(n) = n+1,取其中最耗时部分,并用熟悉的函数表示,则时间复杂度为O(n)。

/*************************************************************************
	> File Name: 001.3或5的倍数.c
	> Author: 陈杰
	> Mail: 18219716075@163.com
	> Created Time: 2021年04月22日 星期四 14时47分10秒
  > 题目描述
  在小于10的自然数中,3 或 5的倍数有3、5、6和9,这些数之和是23。
  求小于1000的自然数中所有或的倍数之和。
************************************************************************/
#include<stdio.h>
int main() {
    int sum = 0;
    for(int i = 1; i < 1000; i++) {
        if(i % 3 == 0 || i % 5 == 0) sum += i;
    }
    printf("%d\n", sum);
    return 0;
}

对其进行优化减少程序的运行时间,现有如下代码,可以计算处语句1执行1次,语句2执行了1次,语句3执行了1次,则T(n) = 3,约掉其中的常数,则时间复杂度为O(1)。

#include<stdio.h>
int main() {
    // 3或者5的倍数数之和 = 3的倍数和 + 5的倍数和 - 15的倍数和
    int t3 = (3 + 999) * (999 / 3) / 2;
    int t5 = (5 + 995) * (999 / 5) / 2;
    int t15 = (15 + 990) * (999 / 15) / 2;
    printf("%d\n", t3 + t5 - t15);
    return 0;
}

举个例子来描述下算法空间复杂度的计算过程。欧拉计划第二题偶斐波那契数列有如下代码,可以计算出 S(n) = n,则空间复杂度为O(n)

/*************************************************************************
	> File Name: 002.偶斐波那契数.c
	> Author: 陈杰
	> Mail: 18219716075@163.com
	> Created Time: 2021年04月22日 星期四 15时34分21秒
  > 题目描述
  斐波那契数列中的每一项都是前两项的和,由1和2开始生成的斐波那契数列的前10项为:1,2,3,5,8,13,21,34,55,89...
  考虑该斐波那契数列中不超过四百万的项,求其中为偶数的项之和。
************************************************************************/
#include<stdio.h>
int main() {
    long long nums[1000], sum = 0;
    nums[0] = 1, nums[1] = 1;
    for(int i = 2; i; i++) {
        nums[i] = nums[i - 2] + nums[i - 1];
        if(nums[i] > 4000000) break;
        if(nums[i] & 1) continue;
        sum += nums[i];
    }
    printf("%lld\n", sum);
    return 0;
}

对其进行优化,减少程序对内存的使用量,现有如下代码,整个程序运行过程中只有三个变量,所以其空间复杂度为O(3)约掉常数后得到其时间复杂度为O(1)

/*************************************************************************
	> File Name: 002.偶斐波那契数.c
	> Author: 陈杰
	> Mail: 18219716075@163.com
	> Created Time: 2021年04月22日 星期四 15时34分21秒
  > 题目描述
  斐波那契数列中的每一项都是前两项的和,由1和2开始生成的斐波那契数列的前10项为:1,2,3,5,8,13,21,34,55,89...
    考虑该斐波那契数列中不超过四百万的项,求其中为偶数的项之和。
 ************************************************************************/
#include<stdio.h>
int main() {
    long long sum = 0;
    int a = 1, b = 2;
    while(b < 4000000) {
        if(!(b&1)) sum += b;
        b = a + b;
        a = b - a;
    }
    printf("%lld\n", sum);
    return 0;
}

时间复杂度与空间复杂度的关系

优秀的程序代码总是运行时间更短,占用系统内存更少。努力减小程序代码的时间复杂度和空间复杂度是每个程序员毕生的追求,但是当代码写到极致时,就会面临一个空间和时间不可兼得的局面,这时候就要根据具体的使用环境和应用场所进行空间和时间的取舍了。

在内存资源相对比较充裕的服务器端,处理速度可能是优先的,经常使用用空间换时间的思维模式来编程,在嵌入式开发等一些内存资源有限的开发环境下,需要尽可能的节省对于系统空间的使用,经常使用用时间换空间的思维模式来编程。下面简单介绍一下,一种空间换时间的算法模式——记忆化。

记忆化为递归提速

题目要求打印出斐波那契数列的前50项, 你们最基础的解法就是利用递归循环至第50项。

/*************************************************************************
	> File Name: fib2.c
	> Author: 陈杰
	> Mail: 18219716075@163.com
	> Created Time: 2021年04月22日 星期四 16时14分21秒
  > 打印出斐波那契数列的前50项
 ************************************************************************/
#include <stdio.h>
long long func(int x) {
    if(x == 0 || x == 1) return 1;
    return func(x - 1) + func(x - 2);
}
int main() {
    for(int i = 1; i < 50; i++) {
        printf("%d : %lld\n", i, func(i));
    }
    return 0;
}

上面的递归因为每次计算新一项是都得从第一项开始算起,所以运行速度很慢。我们对其增加一个记忆数组来为其提速。

/*************************************************************************
  > File Name: fib2.c
  > Author: 陈杰
  > Mail: 18219716075@163.com
  > Created Time: 2021年04月22日 星期四 16时14分21秒
  > 打印出斐波那契数列的前50项
 ************************************************************************/
#include <stdio.h>
long long num[50];
long long func(int x) {
    if(x == 0 || x == 1) return 1;
    if(num[x]) return num[x];
    return num[x] = func(x - 1) + func(x - 2);
}
int main() {
    for(int i = 1; i < 50; i++) {
        printf("%d : %lld\n", i, func(i));
    }
    return 0;
}

这样递归过程的时间就会大大降低,使得其运行效率接近于递推,故有递推加记忆化约等于递推的说法。相比而言递推对斐波那契数列求解是最快的,记忆化的主要用途在于一些递推逻辑比较复杂的情况下,可以简化代码逻辑。下面在具体的例题欧拉计划第14题最长考拉兹序列中继续感受一下。

/*************************************************************************
	> File Name: 014.最长考拉兹序列.c
	> Author: 陈杰
	> Mail: 18219716075@163.com
	> Created Time: 2021年04月22日 星期四 16时30分43秒
  > 题目描述
  考虑如下定义在正整数集上的迭代规则:
  n -> n / 2(若为偶数) n -> 3n+1(若为奇数)
  从13 开始,可以迭代生成如下的序列:
  13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1
  可以看出这个序列(从13开始到1结束)共有10项。尽管还未被证明,但普遍认为,从任何数开始最终都能回到1.(这被称为“考拉兹猜想”)。
  在小于一百万的数中,从哪个数开始迭代生成的序列最长?
  注:在迭代过程中允许出现超过一百万的项。
************************************************************************/
#include<stdio.h>
#define MAX_N 1000000
long long nums[10*MAX_N + 5];
// 计算从x开始的考拉兹序列长度
long long func(long long x) {
    if(x == 1) return 1;
    if(x < 10 * MAX_N && nums[x]) return nums[x];
    long long t;
    if(x & 1) t = func(3 * x + 1) + 1;
    else  t = func(x / 2) + 1;
    if(x < 10 * MAX_N) nums[x] = t;
    return t;
}
int main() {
    long long ans = 0, mmax = 0;
    for(int i = 1; i <= MAX_N; i++) {
        int t = func(i);
        if(t > mmax) {
            mmax = t;
            ans = i;
        }
    }
    printf("%lld\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值