【算法】时间复杂度空间复杂度

0.前言

        算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间空间两个维度来衡量的,即时间复杂度空间复杂度。时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间

本文主要通过大O的渐进表示法对时间复杂度和空间复杂度进行衡量,并通过一些例子说明。

1.大O的渐进表示法

        一般来说算法的空间复杂度和时间复杂度都是一个关于n的函数f(n),例如以下函数func(),我们计算它的时间复杂度。它的执行次数是关于n的函数f(n) = n^2 + 2n + 10 ,但是实际我们计算的时候并不需要精确的执行次数,只需要一个大致的执行次数,这时候就要使用大O的渐进表示法只取其中具有决定性的那一项

int func(int n)
{
	int count = 0;
    //第一段,执行了n*n次
	for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < n; ++j)
        {
            ++count;
        }
    }
    //第二段,执行了2n次
    for (int k = 0; k < 2*n; ++k)
    {
        ++count;
    }
    //第三段,执行了10次
    int x = 10;
    while (x--)
    {
        ++count;
    }

    return count;
}

        1.1 大O符号

        表示一个函数在输入规模趋于无穷大的时候的增长趋势,重点关注的是算法复杂度的上界。记作O(f(n)),f(n)是输入规模为n的某个函数,描述算法的复杂性,然后我们根据推导方法对f(n)做出一些变化。

        1.2 推导方法

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

  • 如果f(n)是一个常数级别的函数,直接用常数1取代,如f(n) = 10,那么它的复杂度就为O(1)。无论这个常数多大。但是如果常数只是该函数的一项,那么直接忽略该常数,进行下面的步骤。
  • 找出f(n)的最高阶的项,例如上面的f(n) = n^2 +2n + 10,它的最高阶的项为n^2
  • 如果最高阶项有常数与它相乘,直接忽略这个系数,例如如果我们得到 f(n) = 3n^2,或者 f(n) = 2n,那么直接将它们的系数改为1,分别变为n^2和n,最终得到复杂度O(n^2)和O(n)。

        1.3 常见的大O复杂度

                        时间复杂度本质计算的是算法的量级。

O(1)常数复杂度
O(n)线性复杂度
O(n^2)平方复杂度(O(n^3),O(n^4)...)
O(logn)对数复杂度
O(nlogn)线性对数复杂度
O(2^n)指数复杂度
O(n!)

阶乘复杂度

        1.4 最坏运行情况

        有些算法的执行次数是不一定的,比如查找算法,我们在查找时可能一次就找到了O(1),也有可能遍历完数组才找到O(n)。对于这种不确定的复杂度,我们可以分情况讨论:最好情况,最坏情况和平均情况。但是一般我们会只关注它的最坏情况

eg.计算如下代码的时间复杂度

const char* strchr( const char* str, int character);

       该函数实现的是在一个字符串中找到一个字符,并且返回第一次出现该字符的指针。此时它的运行次数和时间是一个不确定的值,他的最好情况就是一次找到,最坏情况是N次找到,平均N/2次找到  。因为时间复杂度关注最坏情况,所以这个函数的时间复杂度为O(N).

2.时间复杂度

        时间复杂度描述了算法在输入规模增大时,执行所需时间的增长趋势。以下用例子说明。

2.1 示例一

void Func1(int N)
{
    int count = 0;
    for (int k = 0; k < 2 * N ; ++ k)
    {
        ++count;
    }

    int M = 10;
    while (M--)
    {
        ++count;
    }
    printf("%d\n", count);
}

该函数执行次数为2*N+10,去掉常数10,留下最高阶2*n,去掉系数2,得到n所以时间复杂度为O(n)。 

2.2 示例二

void Func2(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);
}

该函数的执行次数为M+N次,然而M和N都是不确定的,所以它的时间复杂度为O(M+N)。 

2.3 示例三 

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

这段代码的执行次数是100000 次,看起来很大,但也是个常数,所以是常数级别的,它的时间复杂度为O(1)

2.4 👉冒泡排序时间复杂度的计算

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;
    }
}

 时间复杂度为O(N^2),最坏情况就是每一趟都发生了交换,比较次数为n-1 + n-2 + n-3 + ... + 1 = 2/(n^2 - n).  (最好情况就是这个数组本来就是升序排序,比较了n-1次没有发生交换直接结束了,此时时间复杂度为O(N))。

2.5 👉二分查找时间复杂度的计算

int BinarySearch(int* a, int n, int x)
{
    assert(a);
    int begin = 0;
    int end = n-1;
    // [begin, end]:begin和end是左闭右闭区间,因此有=号
    while (begin <= end)
    {
        int mid = begin + ((end-begin)>>1);
        if (a[mid] < x)
            begin = mid+1;
        else if (a[mid] > x)
            end = mid-1;
        else
            return mid;
    }
    return -1;
}

 最好情况是一次找到,最坏情况是log(2)N次找到,所以时间复杂度是O(logN)

 区间数据个数:
    N
    N/2
    N/2/2
    ...
    N/2/2/.../2 = 1
    假设查找x次,2^x=N。可以得到x = logN(log以2为底N的对数次,但是有些文本不方便,可以直接简写成logN,以2为底的可以省略,其他的要写出来)

 2.6 👉阶乘递归函数的时间复杂度

long long Fac(size_t N)
{
    if(0 == N)
        return 1;
    return Fac(N-1)*N;
}

大概就是被调用N+1次,每次调用的时间复杂度是O(N),所以这整个递归函数的时间复杂度就是O(N)

递归算法的时间复杂度是多次调用的次数的累加,eg如果这个函数是这样的👇

long long Fac(size_t N)
{
    if(0 == N)
        return 1;
    for (size_t i = 0; i < N; i++)
    {
        ...
    }
    return Fac(N-1)*N;
}

 此时每次调用执行次数都不同,分别是N,N-1,...,1,全部相加,时间复杂度就为O(N^2)

(时间复杂度是一个大概的计算,一般如果算这些语句中比较不确定或者庞大的部分,比如循环...有些定义语句,返回语句不用加上算得那么精准)

2.7 👉斐波那契递归的时间复杂度 

long long Fib(size_t N)
{
    if(N < 3)
        return 1;
    return Fib(N-1) + Fib(N-2);
}

斐波那契数列的递归时间复杂度为O(2^N),因为他又很多重复的执行,导致在时间上的效率低下,如果用循环来计算时间复杂度是O(N)。它在函数体内的执行次数也是O(1)级别的,但是它的调用次数非常多,大致是一个等比数列的相加(当然也不完全是一个等比数列,画图就会发现有一边计算的会少一个,不过这也不影响) 


3.时间复杂度

        空间复杂度的思想与时间复杂度相似,它计算的是一个算法在运行过程中额外临时占存储空间大小的度量。空间复杂度算的不是程序占用了多少bytes的空间,而是变量的个数。也使用大O渐进表示法。
        注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。


以下直接通过示例说明

3.1 冒泡排序空间复杂度

        冒泡排序在排序过程中并没有新建数组,最多也就几个变量,是常数级别的,因此可以视为没有额外开辟空间,所以它的空间复杂度就是O(1)

 3.2 斐波那契数列空间复杂度

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
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项,函数在函数体内开辟了n+1个空间,这就是额外开辟的空间,所以它的空间复杂度是O(N)

3.3  阶乘递归空间复杂度

        每次递归栈帧需要占常数个空间O(1),需要建立N个栈帧,所以空间复杂度为O(N)。

 3.4 斐波那契递归空间复杂度

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
    if(N == 0)
        return 1;
    return Fac(N-1)*N;
}

(递归的最大问题:深度太深,容易栈溢出)
时间是累计的,空间是可以重复利用的 每次递归返回的时候多个函数空间共用同一个栈帧,最多用N块空间。递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)

虽然这个递归的时间复杂度是O(2^N),但是它不是每次调用都是需要新的空间建立栈帧的,可以从以下代码理解。
以下代码最后输出两个相同的地址(函数地址是不一样的,函数地址不是栈帧地址,是指令的地址),可能稍微变动就不相同了,以下两个逻辑相似。


-THE END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/927282.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java全栈day10--后端Web基础(基础知识)

引言&#xff1a;只要能通过浏览器访问的网站全是B/S架构&#xff0c;其中最常用的服务器就是Tomcat 在浏览器与服务器交互的时候采用的协议是HTTP协议 一、Tomcat服务器 1.1介绍 官网地址&#xff1a;Apache Tomcat - Welcome! 1.2基本使用(网上有安装教程&#xff0c;建议…

webrtc ios h264 硬编解码

webrtc ios h264 硬编解码 一 ios 系统支持 从ios8开始&#xff0c;苹果公司开放了硬解码和硬编码API&#xff08;即 VideoToolbox.framework API&#xff09; 二 主要api 1 主要解码函数 VTDecompressionSessionCreate // 创建解码 session VTDecompressionSession…

【JavaEE】JavaEE、web 开发、框架(Spring) 、Maven

文章目录 一、JavaEE 发展历程二、什么是 web 开发1、什么是 web 开发&#xff1f;2、web 网站的工作流程 三、框架1、什么是框架&#xff1f;2、为什么要学框架&#xff1f;3、框架的优点&#xff08;Spring Boot VS Servlet&#xff09; 四、Maven 一、JavaEE 发展历程 Java…

【RISC-V CPU debug 专栏 2 -- Debug Module (DM), non-ISA】

文章目录 调试模块(DM)功能必须支持的功能可选支持的功能兼容性要求规模限制Debug Module Interface (DMI)总线类型地址与操作地址空间控制机制Debug Module Interface Signals请求信号响应信号信号流程Reset Control复位控制方法全局复位 (`ndmreset`)Hart 复位 (`hartreset…

Scala学习记录,全文单词统计

package test32 import java.io.PrintWriter import scala.io.Source //知识点 // 字符串.split("分隔符"&#xff1a;把字符串用指定的分隔符&#xff0c;拆分成多个部分&#xff0c;保存在数组中) object test {def main(args: Array[String]): Unit {//从文件1.t…

使用 Certbot 为 Nginx 自动配置 SSL 证书

1.安装Certbot和Nginx插件 sudo apt-get update sudo apt-get install certbot python3-certbot-nginx 2.获取和安装证书 运行Certbot自动安装SSL证书。注意替换 your_domain sudo certbot --nginx -d your_domain Certbot将自动与Lets Encrypt的服务器通信&#xff0c;验证域…

Java之深入理解HashMap

Java之深入理解HashMap 引言 HashMap是Java程序员使用频率最高的一种映射&#xff08;<Key,Value>键值对&#xff09;数据结构&#xff0c;它继承自AbstractMap&#xff0c;又实现了Map类。 本文将深入源码解析一下HashMap的底层原理。 数据结构 HashMap底层通过维护…

HTTP 探秘之旅:从入门到未来

文章目录 导言&#xff1a;目录&#xff1a;第一篇&#xff1a;HTTP&#xff0c;互联网的“快递员”第二篇&#xff1a;从点开网页到看到内容&#xff0c;HTTP 究竟做了什么&#xff1f;第三篇&#xff1a;HTTP 的烦恼与进化史第四篇&#xff1a;HTTP 的铠甲——HTTPS 的故事第…

Docker 容器网络创建网桥链接

一、网络&#xff1a;默认情况下&#xff0c;所有的容器都以bridge方式链接到docker的一个虚拟网桥上&#xff1b; 注意&#xff1a;“172.17.0.0/16”中的“/16”表示子网掩码的长度为16位&#xff0c;它表示子网掩码中有16个连续的1&#xff0c;后面跟着16个连续的0。用于区分…

springboot366高校物品捐赠管理系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 高校物品捐赠管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff…

upload-labs 靶场(11~21)

免责声明 本博客文章仅供教育和研究目的使用。本文中提到的所有信息和技术均基于公开来源和合法获取的知识。本文不鼓励或支持任何非法活动&#xff0c;包括但不限于未经授权访问计算机系统、网络或数据。 作者对于读者使用本文中的信息所导致的任何直接或间接后果不承担任何…

jenkins+github+springboot自动部署

背景&#xff1a; 最近看流水线有点意思&#xff0c;就说自己也搞一套。 预期效果&#xff1a; idea提交代码后&#xff0c;GitHub接收&#xff0c;jenkins自动部署。【后续加个自动部署时的代码检查、单元测试、安全测试、sonarqube】 思路分析: idea上的spring代码push到gi…

kafka数据在服务端时怎么写入的

学习背景 接着上篇&#xff0c;我们来聊聊kafka数据在服务端怎么写入的 服务端写入 在介绍服务端的写流程之前&#xff0c;我们先要理解服务端的几个角色之间的关系。 假设我们有一个由3个broker组成的kafka集群&#xff0c;我们在这个集群上创建一个topic叫做shitu-topic&…

Springboot——SseEmitter流式输出

文章目录 前言SseEmitter 简介测试demo注意点异常一 ResponseBodyEmitter is already set complete 前言 最近做AI类的开发&#xff0c;看到各大AI模型的输出方式都是采取的一种EventStream的方式实现。 不是通常的等接口处理完成后&#xff0c;一次性返回。 而是片段式的处理…

【深度学习|目标跟踪】StrongSORT 详解(以及StrongSORT++)

StrongSort详解 1、论文及源码2、DeepSORT回顾3、StrongSORT的EMA4、StrongSORT的NSA Kalman5、StrongSORT的MC6、StrongSORT的BOT特征提取器7、StrongSORT的AFLink8、StrongSORT的GSI模块 1、论文及源码 论文地址&#xff1a;https://arxiv.org/pdf/2202.13514 源码地址&#…

AntFlow 0.20.0版发布,增加多数据源多租户支持,进一步助力企业信息化,SAAS化

传统老牌工作流引擎比如activiti,flowable或者camunda等虽然功能强大&#xff0c;也被企业广泛采用&#xff0c;然后也存着在诸如学习曲线陡峭&#xff0c;上手难度大&#xff0c;流程设计操作需要专业人员&#xff0c;普通人无从下手等问题。。。引入工作流引擎往往需要企业储…

【设计模式系列】工厂方法模式(二十一)

一、什么是工厂方法模式 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;其核心目的是定义一个创建对象的接口&#xff0c;但让实现这个接口的子类来决定实例化哪一个类。工厂方法模式让类的实例化推迟到子类中进行&#xff0c;…

HCIE:详解OSPF,从基础到高级特性再到深入研究

目录 前言 一、OSPF协议基本原理 简介 基本原理 OSPF路由器类型 OSPF网络类型 OSPF报文类型和封装 OSPF邻居的建立的维护 DR和BDR的选举 伪节点 LSDB的更新 OSPF的配置 二、OSPF的高级特性 虚连接&#xff08;Virtual-Link&#xff09; OSPF的LSA和路由选择 OSPF…

分享一款 Vue 图片编辑插件 (推荐)

&#x1f4a5;本篇文章给大家分享一款强大到没朋友的Vue图片编辑插件&#xff0c;可以对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本等&#xff0c;快来试试并收藏吧&#xff01;&#x1f495; 这是一款对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本在线处理的图片处…

【时间之外】IT人求职和创业应知【53】-东莞也转型

目录 新闻一&#xff1a;Freysa挑战赛&#xff1a;人类智慧与策略战胜AI&#xff0c;奖金高达4.7万美元 新闻二&#xff1a;中国生成式AI用户规模突破2.3亿&#xff0c;行业应用广泛 新闻三&#xff1a;2024东莞智能终端新技术推广会圆满举行&#xff0c;聚焦AI与智能终端融…