首页 > 编程语言 >【算法】算法性能分析

【算法】算法性能分析

时间:2023-09-23 10:55:05浏览次数:38  
标签:分析 return 递归 int 性能 算法 内存 复杂度

1 时间复杂度

1.1 知识点

时间复杂度是一个函数,它定性描述该算法的运行时间

通常会估算算法的操作单元数量来代表程序消耗的时间。假设算法的问题规模为n,那么操作单元数量便用函数f(n)来表示,随着数据规模n的增大,算法执行时间的增长率和f(n)的增长率相同,这称作为算法的渐近时间复杂度,简称时间复杂度,记为 O(f(n))。

大O用来表示上界,就是对任意数据输入算法的运行时间的上界。数据用例不一样,时间复杂度也会不同。

面试中算法的时间复杂度指的都是一般情况:

时间复杂度省略常数项系数,是因为一般情况下都默认数据规模足够大,基于这样的事实,给出的算法时间复杂度的一个排行如下所示

O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(nlogn)线性对数阶 < O(n^2)平方阶 < O(n^3)立方阶 < O(2^n)指数阶

1.2 例子

题目:找出n个字符串中相同的两个字符串(假设这里只有两个相同的字符串)。

解法一:暴力枚举,时间复杂度是O(m × n × n)

解法二:先排序再遍历(两个相同的字符串挨在一起),总共的时间复杂度是 O(m × n × logn + n × m),简化后是O(m × n × log n)

很明显O(m × n × logn) 要优于O(m × n × n)!

2 算法超时

一般OJ超时时间是1s,如果是$O(n)$的算法 ,可估算出n是多大的时候算法会超时 ⇒ 考虑log(n)解法

3 递归的时间复杂度

题目:求x的n次方。

解法一

int function1(int x, int n) {
    int result = 1;  // 注意 任何数的0次方等于1
    for (int i = 0; i < n; i++) {
        result = result * x;
    }
    return result;
}

时间复杂度为O(n)

解法二

int function2(int x, int n) {
    if (n == 0) {
        return 1; // return 1 同样是因为0次方是等于1的
    }
    return function2(x, n - 1) * x;
}

递归算法的时间复杂度本质上看: 递归的次数 * 每次递归中的操作次数

时间复杂度为 n × 1 = O(n)

解法三

int function3(int x, int n) {
    if (n == 0) return 1;
    if (n == 1) return x;
    if (n % 2 == 1) {
        return function3(x, n / 2) * function3(x, n / 2)*x;
    }
    return function3(x, n / 2) * function3(x, n / 2);
}

把递归抽象成一棵满二叉树,假设n=16:

这棵树上每一个节点就代表着一次递归并进行了一次相乘操作。如果是求x的n次方,这个递归树的节点计算如下图所示(m为深度,从0开始):

时间复杂度忽略掉常数项-1之后,这个递归算法的时间复杂度依然是O(n)

解法四

int function4(int x, int n) {
    if (n == 0) return 1;
    if (n == 1) return x;
    int t = function4(x, n / 2);// 这里相对于function3,是把这个递归操作抽取出来
    if (n % 2 == 1) {
        return t * t * x;
    }
    return t * t;
}

这里仅仅有一个递归调用,且每次都是n/2 ,所以这里我们一共调用了$log_2n$次,每次递归都做了一次乘法操作,也是一个常数项的操作**,那么这个递归算法的时间复杂度才是真正的O(logn)**

4 空间复杂度

空间复杂度是对一个算法在运行过程中占用内存空间大小的量度,记做S(n)=O(f(n)。

!空间复杂度是考虑程序运行时占用内存的大小,而不是可执行文件的大小

!空间复杂度是预先大体评估程序内存使用的大小,而不是准确算出内存

空间复杂度O(1):

int j = 0;
for (int i = 0; i < n; i++) {
    j++;
}

随着n的变化,所需开辟的内存空间并不随着n的变化而变化,算法空间复杂度为一个常量。

空间复杂度O(n):

int* a = new int(n);
for (int i = 0; i < n; i++) {
    a[i] = i;
}

定义的数组大小为n,开辟的内存大小和输入参数n保持线性增长。

在递归的时候,会出现空间复杂度为logn的情况

递归算法的空间复杂度 = 每次递归的空间复杂度 * 递归深度

5 递归算法的性能分析

5.1 求斐波那契数

解法一:

int fibonacci(int i) {
       if(i <= 0) return 0;
       if(i == 1) return 1;
       return fibonacci(i-1) + fibonacci(i-2);
}

一棵深度为k(按根节点深度为1)的二叉树最多可以有 $2^k - 1$ 个节点,该算法时间复杂度为$O(2^n)$

每次递归的空间复杂度是O(1), 调用栈深度为n,这段代码的空间复杂度是O(n)

解法二:

int fibonacci(int first, int second, int n) {
    if (n <= 0) {
        return 0;
    }
    if (n < 3) {
        return 1;
    }
    else if (n == 3) {
        return first + second;
    }
    else {
        return fibonacci(second, first + second, n - 1);
    }
}

用first和second来记录当前相加的两个数值,不用两次递归;递归了n次,时间复杂度是 O(n)

同理递归的深度依然是n,每次递归所需的空间是常数,空间复杂度是O(n)

解法三(非递归):

int fibonacci(int n) {
    if (n <= 0) {
        return 0;
    } else if (n == 1) {
        return 1;
    } else {
        int prev = 0;
        int curr = 1;
        for (int i = 2; i <= n; ++i) {
            int next = prev + curr;
            prev = curr;
            curr = next;
        }
        return curr;
    }
}

5.2 二分查找法

int binary_search( int arr[], int l, int r, int x) {
    if (r >= l) {
        int mid = l + (r - l) / 2;
        if (arr[mid] == x)
            return mid;
        if (arr[mid] > x)
            return binary_search(arr, l, mid - 1, x);
        return binary_search(arr, mid + 1, r, x);
    }
    return -1;
}

时间复杂度是O(logn)

空间复杂度看每次递归的空间复杂度和递归深度。需要注意在C/C++中函数传递数组参数,不是整个数组拷贝一份而是传入数组首元素地址,每一层递归都公用一块数组地址空间。所以每次递归的空间复杂度是常数O(1)。递归深度是logn,空间复杂度为 1 * logn = O(logn)。

❗注意所用语言在传递函数参数时,是拷贝整个数值还是拷贝地址。如果是拷贝整个数值,那么该算法的空间复杂度就是O(nlogn)

6 内存对齐

为什么会有内存对齐?

  1. 平台原因:不是所有的硬件平台都能访问任意内存地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。为了同一个程序可以在多平台运行,需要内存对齐。
  2. 硬件原因:经过内存对齐后,CPU访问内存的速度大大提升。

CPU读取内存不是一次读取单个字节,而是一块一块的来读取内存,块的大小可以是2,4,8,16个字节,具体取多少个字节取决于硬件。

此时,直接将地址4,5,6,7处的四个字节数据读取到即可。

此时一共需要两次寻址,一次合并的操作。

编译器一般都会做内存对齐的优化操作,当考虑程序真正占用的内存大小的时候,也需要认识到内存对齐的影响。

标签:分析,return,递归,int,性能,算法,内存,复杂度
From: https://www.cnblogs.com/Aikoin/p/17723972.html

相关文章

  • 【POJ 1521】Entropy 题解(贪心算法+优先队列+哈夫曼树)
    熵编码器是一种数据编码方法,通过对删除了“浪费”或“额外”信息的消息进行编码来实现无损数据压缩。换句话说,熵编码去除了最初不需要的信息,以准确编码消息。高度的熵意味着一条消息包含大量浪费的信息;以ASCII编码的英文文本是具有极高熵的消息类型的示例。已经压缩的消息,如JPEG图......
  • Kafka消息压缩算法性能调优与选择
    前言Kafka作为一款高性能的分布式消息队列,其消息压缩算法的选择和调优对于系统性能的提升至关重要。本文将深入探讨Kafka消息压缩算法的性能调优和选择。压缩算法的选择Kafka支持多种压缩算法,包括gzip、snappy和lz4。这些算法各有优缺点,需要根据实际情况进行选择。gzipgzip是......
  • Kafka消息消费者位移存储性能测试
    背景Kafka是一个高性能、分布式的消息队列,被广泛应用于大数据领域。在Kafka中,消费者位移存储是非常重要的一部分,它记录了消费者消费消息的位置,以便在消费者宕机或者重启后能够继续消费未消费的消息。在实际应用中,消费者位移存储的性能对于Kafka的整体性能有着重要的影响。本文将......
  • ClickHouse数据缓存与性能优化技术实现最佳实践与案例
    前言ClickHouse是一款高性能的列式存储数据库,它的性能在处理海量数据时非常出色。但是,在实际应用中,我们还需要考虑如何进一步优化ClickHouse的性能,特别是在数据缓存方面。本文将深入探讨ClickHouse的数据缓存与性能优化技术实现最佳实践与案例。ClickHouse数据缓存ClickHouse的......
  • 框架分析(6)-Ruby on Rails
    (框架分析(6)-RubyonRails)专栏介绍link主要对目前市面上常见的框架进行分析和总结,希望有兴趣的小伙伴们可以看一下,会持续更新的。希望各位可以监督我,我们一起学习进步。RubyonRailsRubyonRails(简称Rails)是一种使用Ruby编程语言开发的开源Web应用程序框架。它遵循MVC(Mode......
  • QEMU 8.1 正式发布,提升 CPU 性能、支持 LoongArch LSX 扩展
    导读QEMU8.1已正式发布,这是QEMU8.0系列的首个重要更新。主要变化支持IntelGraniteRapids的新x86CPU模型微代码生成器(TinyCodeGenerator,TCG)支持RDPID指令,AES指令可以使用主机处理器上的AES加速,以及其他新功能从支持BF16扩展到Zfa扩展、Z......
  • (七)Unity性能优化-资源导入工作流
    原链接:https://github.com/lwwhb/Unity2022_SUNTAIL_Stylized_Fantasy_Village_Optimization资源导入工作流的三种方案1.手动编写工具优点:根据项目特点自定义安排导入工作流,并且可以和后续资源制作与大包工作流结合缺点:存在开发和维护成本,会让编辑器菜单界面变得复杂,对新人理......
  • 算法训练day17 LeetCode 110
    算法训练day17LeetCode110.257.404110平衡二叉树题目110.平衡二叉树-力扣(LeetCode)题解代码随想录(programmercarl.com)当子树已经不平衡,直接返回-1.平衡则返回子数高度进行更高树间的高度比较classSolution{public:intgetHeight(TreeNode*node)......
  • 拓展欧几里得算法揭秘
    最大公约数更相减损术:\(\gcd(x,y)=\gcd(y-x,x)(x\leqy)\)。设\(\gcd(x,y)=k,\gcd(p,q)=1,x=kp,y=kq\)。那么\(\gcd(y-x,x)=\gcd(kq-kp,kp)=k\times\gcd(q-p,p)\)。设\(\gcd(q-p,p)=r,q-p=rb,p=ra\)。那么\(q=r(a+b)\)。因为\(\gcd(p,q)=1=\gcd(ra,r(a+b))\)。所以......
  • 算法训练day16 LeetCod 104
    算法训练day16LeetCod104.111.222104.二叉树的最大深度题目104.二叉树的最大深度-力扣(LeetCode)题解代码随想录(programmercarl.com)递归采用后序的遍历顺序,在根节点处做高度数据的处理classSolution{public:intgetdepth(TreeNode*node){......