一文彻底理解时间复杂度和空间复杂度(附实例)

news2024/9/30 11:24:29

目录

  • 1 P=NP?
  • 2 时间复杂度
    • 2.1 常数阶复杂度
    • 2.2 对数阶复杂度
    • 2.3 线性阶复杂度
    • 2.4 平方阶复杂度
    • 2.5 指数阶复杂度
    • 2.6 总结
  • 3 空间复杂度

1 P=NP?

P类问题(Polynomial)指在多项式时间内能求解的问题;NP类问题(Non-Deterministic Polynomial)指在多项式时间内能验证一个解的问题。

在这里插入图片描述

问题的归约(reduction)指将一个问题的求解等效为另一个问题的求解,其中这种等效具有提升普遍性、加大复杂度的趋势,且问题归约可传递。举例而言,可以将求解一元一次方程归约为求解一元二次方程。

若任意一个NP类问题都能在多项式时间内归约到某个NP问题,则该问题被称为NP完全问题(NP-Complete);若无法归约到某个NP问题,则该问题被称为NP难问题(NP-Hard)

P类问题本身是NP的,因为能在多项式时间内求解必然能在多项式时间内验证解。但反之,NP问题是否是P问题的论断称为“P=NP?”,该论断被列为千禧七大难题之首,暂未被证明或证伪。

2 时间复杂度

一般地,算法中基本操作重复执行的次数是问题规模 n n n的某个函数 f ( n ) f(n) f(n)。在计算机科学中用时间复杂度(Time Complexity)定性描述一个算法的运行时耗——体现在问题规模 n n n变化 c c c倍后算法的执行效率,而非针对一个特定的 n n n,记为

T ( n ) = O ( f ( n ) ) T\left( n \right) =O\left( f\left( n \right) \right) T(n)=O(f(n))

其中 O ( ⋅ ) O\left( \cdot \right) O()是复杂度度量函数,不包括输入的低阶项和首项系数(非常数项)。必须指出,使用 O ( ⋅ ) O\left( \cdot \right) O()属于渐进时间复杂度——输入值大小趋近无穷时的情况,否则不能忽略其他低阶项的影响。常见的时间复杂度如下

2.1 常数阶复杂度

常数阶复杂度记为 O ( a ) O(a) O(a)

示例

// 计算 1 + 2 + 3 + ... + n 的值
int sum(int n)
{
    return (1 + n) * n / 2;
}

解释:对任意问题规模 ,算法均只执行一次,故认为算法时间复杂度为 O ( 1 ) O(1) O(1)

2.2 对数阶复杂度

常数阶复杂度记为 O ( log ⁡ n ) O\left( \log n \right) O(logn)

示例

/*
 * 二分查找
 *    A[] : 待查找的数组(已排序)
 *      n : 数组长度
 * target : 查找的目标值
 */
int binarySearch(int A[], int n, int target) {
    int lt = 0, rt = n;
    while(lt < rt){
        int mid = lt + (rt - lt)/2;
        if(A[mid] == target) return mid;
        else if(A[mid] > target) rt = mid;
        else lt = mid + 1;
    }
    return -1; // 查找不到
}

解释:二分查找算法每次迭代排除一半元素,因此第 m m m次迭代后剩余待查找元素个数为 n / 2 m {{n}/{2^m}} n/2m,最坏情况下排除到只剩最后一个值后得到结果——结果为该值或查找不到,即令 n / 2 m = 1 {{n}/{2^m}}=1 n/2m=1,则 f ( n ) = m = log ⁡ 2 n f\left( n \right) =m=\log _2n f(n)=m=log2n,故认为算法时间复杂度为 O ( log ⁡ n ) O\left( \log n \right) O(logn)

2.3 线性阶复杂度

示例

// 计算 1 + 2 + 3 + ... + n 的值
int sum(int n)
{
    int sum = 0;
    for(int i = 1; i <= n; i++) {
        sum += i;
    }
    return sum
}

解释:对任意问题规模 n n n,算法均执行 n n n次,故认为算法时间复杂度为 O ( n ) O(n) O(n)

2.4 平方阶复杂度

示例

/*
 * 冒泡排序
 * arr[] : 待排序数组
 *     n : 数组长度
 */
void bubbleSort(int[] arr, int n) {
    if(n == 0 || n == 1) return;
    for(int i = 0; i < n - 1; ++i) {
        for(int j = 0; j < n - i - 1; ++j) {
            if(a[j] > a[j+1]) swap(a[j], a[j+1]);
        }
    }
}

解释:冒泡排序算法共 n − 1 n-1 n1次迭代,每次迭代循环比较 n − i − 1 n-i-1 ni1次,故总迭代次数为 ( n − 1 ) + ( n − 2 ) + ⋯ + 1 = n ( n − 1 ) / 2 \left( n-1 \right) +\left( n-2 \right) +\cdots +1={{n\left( n-1 \right)}/{2}} (n1)+(n2)++1=n(n1)/2,故认为算法时间复杂度为 O ( n 2 ) O\left( n^2 \right) O(n2)

2.5 指数阶复杂度

示例

// 计算斐波那契数列
int fibonacci(int n)
{
    if (n<=0) return 0;
    if (n==1) return 1;
    return fb(n - 1) + fb(n - 2);
}

解释:斐波那契算法本质上是二阶常系数差分方程 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f\left( n \right) =f\left( n-1 \right) +f\left( n-2 \right) f(n)=f(n1)+f(n2),其特征根为 x 1 , 2 = 1 ± 5 2 x_{1,2}=\frac{1\pm \sqrt{5}}{2} x1,2=21±5 ,则该差分方程通解为 f ( n ) = c 1 ( 1 + 5 2 ) n + c 2 ( 1 − 5 2 ) n f\left( n \right) =c_1\left( \frac{1+\sqrt{5}}{2} \right) ^n+c_2\left( \frac{1-\sqrt{5}}{2} \right) ^n f(n)=c1(21+5 )n+c2(215 )n,代入初始条件 f ( 0 ) = 0 f\left( 0 \right) =0 f(0)=0 f ( 1 ) = 1 f\left( 1 \right) =1 f(1)=1解得 c 1 c_1 c1 c 2 c_2 c2后即得

f ( n ) = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] f\left( n \right) =\frac{1}{\sqrt{5}}\left[ \left( \frac{1+\sqrt{5}}{2} \right) ^n-\left( \frac{1-\sqrt{5}}{2} \right) ^n \right] f(n)=5 1[(21+5 )n(215 )n]

故认为算法时间复杂度为 O ( a n ) O\left( a^n \right) O(an)

2.6 总结

特别地,当 n n n处于复杂度底数位置时称为多项式时间复杂度,例如常数阶、对数阶、线性阶等;当 n n n处于复杂度指数位置时称为超多项式时间复杂度,例如指数阶、阶乘阶等。一般地,计算机只能处理多项式时间复杂度算法,而无法忍受超多项式时间算法中问题规模的些许增长带来的爆炸式耗时,因此将多项式时间视为算法在时间复杂度层面是否有效的分水岭,如图所示。

在这里插入图片描述

3 空间复杂度

在计算机科学中用空间复杂度(Space Complexity)定性描述一个算法的内存占用——体现在问题规模 n n n变化 c c c倍后算法临时占用内存的增长状况,而非针对一个特定的 n n n,记为

S ( n ) = O ( f ( n ) ) S\left( n \right) =O\left( f\left( n \right) \right) S(n)=O(f(n))

算法空间复杂度分析与时间复杂度类似,区别在于其关注的不是基础操作的重复次数,而是算法运行时堆栈中的内存消耗,在递归算法中尤为明显。一般地,算法复杂性分析优先考察时间复杂度,而假设空间不受限;对空间复杂度的考察主要集中在嵌入式领域。

下面是归并排序的实例。

/*
 * 归并排序
 * a[] : 待排序数组
 *  lt : 排序左索引
 *  rt : 排序右索引
 * p[] : 临时数组,存储排序元素
 */
void merge(int a[], int lt, int rt, int p[]){
    int mid = (rt - lt)/2 + lt;
    int i = lt, j = mid + 1;
    int k = 0;
    // 合并
    while(i <= mid && j <= rt){ 
        if(a[i] <= a[j])  p[k++] = a[i++];
        else              p[k++] = a[j++];
    }
    // 合并剩余
    while(i <= mid)         p[k++] = a[i++];
    while(j <= rt)          p[k++] = a[j++];
    // 重新赋值回去
    for(i = 0; i < k; ++i)  a[lt+i] = p[i];
}
 
// 划分
void mergeSort(int a[], int lt, int rt, int p[]){
    if(lt < rt){
        int mid = (rt - lt)/2 + lt;
        mergeSort(a, lt, mid, p);   // 递归排序 lt ~ mid
        mergeSort(a, mid+1, rt, p); // 递归排序 mid+1 ~ rt
        merge(a, lt, rt, p);        // 合并 lt ~ rt
    }
}

设问题规模为 n n n,即待排序元素为 n n n个,则递归调用时最坏情况下会产生 log ⁡ 2 n \log _2n log2n层递归树。若临时数组在全局作用域中开辟,则递归过程中不再开辟新的内存空间,空间复杂度为 O ( n ) O(n) O(n);若临时数组在栈中申请,则每层递归都要开辟一次长度为 n n n的临时空间,空间复杂度为 O ( n log ⁡ 2 n ) O\left( n\log _2n \right) O(nlog2n)


🔥 更多精彩专栏

  • 《ROS从入门到精通》
  • 《Pytorch深度学习实战》
  • 《机器学习强基计划》
  • 《运动规划实战精讲》

👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据中心UPS监控,不服不行!

UPS作为关键的电力保障设备&#xff0c;它在电力中断或波动的情况下&#xff0c;为电子设备提供稳定的备用电源&#xff0c;以防止数据丢失、设备损坏或生产中断。 通过远程监控、电池健康检测、负载管理、警报通知等功能&#xff0c;UPS监控确保了系统的高效运行和可靠性。同时…

Windows下安装tomcat无法启动问题汇总和解决

tomcat在初学的时候安装总是出现各种问题&#xff0c;最近重新安装了一次&#xff0c;居然也被一些小问题导致无法启动了&#xff0c;特此写下这篇文章&#xff0c;希望能帮助到大家 导致tomcat启动失败原因 1、未配置tomcat环境变量: CATALINA_HOME&#xff1b;然后path环境…

扭矩张力控制和速度张力控制详细介绍

张力控制的开环和闭环相关算法介绍,请查看下面文章链接: PLC张力控制(开环闭环算法分析)_张力控制plc程序实例_RXXW_Dor的博客-CSDN博客里工业控制张力控制无处不在,也衍生出很多张力控制专用控制器,磁粉制动器等,本篇博客主要讨论PLC的张力控制相关应用和算法,关于绕…

LeetCode(力扣)404. 左叶子之和Python

LeetCode404. 左叶子之和 题目链接代码 题目链接 左叶子之和 代码 递归 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right …

【JavaEE进阶】SpringMVC

文章目录 一. 简单认识SpringMVC1. 什么是SpringMVC?2. SpringMVC与MVC的关系 二. SpringMVC1. SpringMVC创建和连接2. SpringMVC的简单使用2.1 RequestMapping 注解介绍2.2 RequestMapping支持的请求类型2.3 GetMapping 和 PostMapping 3. 获取参数3.1 传递单个参数3.2 传递对…

2023年跑步耳机性价比最高的都在这里了!不容错过

你还在为如何选择合适的跑步耳机而困扰吗&#xff1f;身为一个常年的跑步的我&#xff0c;在选择运动耳机的时候我会从价格、性能、舒适度、耐用性等多个方面去进行了比较&#xff0c;这样才能够找到最适合自己的跑步耳机。那么下面我就给大家推荐几款具有性价比的跑步耳机&…

项目管理工具,让项目团队更高效

随着项目管理的发展&#xff0c;出现个各种各样的项目管理工具&#xff0c;帮助项目团队提高项目管理的效率和质量&#xff0c;降低成本和风险&#xff0c;增强团队的协作和沟通效率&#xff0c;实现项目的成功实施和持续发展。 1、项目计划是项目管理中重要的一环 在传统的…

4.react useContext使用与常见问题

1. 在函数组件实现跨组件通信的方式 2. 注册Context将value传递给子组件let MyContext React.createContext(默认值); <MyContext.Provider value{} > let value useContext(MyContext)<!DOCTYPE html> <html lang"en"><head><meta cha…

【正点原子STM32连载】第十五章 窗口看门狗实验 摘自【正点原子】APM32F407最小系统板使用指南

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html# 第十…

认识Mybatis并实现增删查改

目录 一.Mybatis特性 二.常见持久层技术的比较 三.搭建Mybaits环境 四.使用Mybatis 五.通过Mybatis实现增删改 六.实现数据库的查询操作 一.Mybatis特性 定制化SQL&#xff1a;MyBatis允许开发人员编写、优化和管理自定义的SQL语句&#xff0c;可以满足复杂查询和存储过程等…

Vitepress

Vitepress 版本&#xff1a;1.0.0-rc.1 这里使用在Vue3组件库中作为项目文档说明使用&#xff0c;另外版本见差异有点大&#xff0c;如版本不一致请参照官方文档 1、安装&初始化 1.1、安装 yarn add vitepress1.0.0-rc.11.2、初始化 PS D:\WebstromProject\chenxing>…

CloudQuery实战 | 谁说没有一款一体化数据库操作管控云平台了?

文章目录 CloudQuery询盾的地址CloudQuery主页统一入口数据库归纳SQL编辑器权限管控审计中心数据保护数据变更 CloudQuery文档中心了解CloudQuery快速入门安装步骤社区版v2.1.0操作手册1数据查询更新日志 CloudQuery社区和活动 CloudQuery线上实战线上实战主页面展示及数据操作…

CAD哪个版本最好用?学习一下CAD版本转换方法

CAD即计算机辅助设计&#xff0c;是一个制图软件&#xff0c;用于绘制建筑、机械、电子等领域的图纸。CAD文件通常被称为“图纸”或“工程图”。 CAD文件通常在以下方面使用&#xff1a; 1. 建筑&#xff1a;建筑师使用CAD文件来创建建筑物的平面图、立体图和剖面图。 2. 机…

Docker部署LNMP

Docker部署LNMP 一、安装docker1.安装docker2.镜像下载 二、部署MySQL1.获取镜像2.创建启动容器创建启动容器 huahua_mysql 三、部署PHP1.获取镜像2.创建容器3.查看信息 四、安装nginx1.获取镜像2.创建运行容器3.修改nginx配置文件 五、总结1. 安装Docker和Docker Compose&…

IO模型(阻塞IO、非阻塞IO、IO多路复用)

1.阻塞IO&#xff1a;最常用&#xff0c;最简单&#xff0c;效率最低 2.非阻塞 IO--->fcntl&#xff1a;获取或设置fd所指定的文件描述符的属性 箭头地方二选一 3.IO多路复用&#xff1a;允许同时对多个IO进行控制 select&#xff1a;内核同时检测多个IO&#xff0c;一…

金额千位符自定义指令

自定义指令文件 moneyFormat.js /*** v-money 金额千分位转换*/export default {inserted: inputFormatter({// 格式化函数formatter(num, util) {if(num null || num || num undefined || typeof(num) undefined){return }if(util 万元 || util 万){return formatMone…

SHELL 基础 显示字符颜色, 修改历史命令,Linux里的命令 执行顺序

echo 打印命令 &#xff1a; 显示字符串 &#xff1a; [rootserver ~]# echo this is SHELL language this is SHELL language [rootserver ~]# echo this is SHELL language this is SHELL language [rootserver ~]# echo "this is SHELL language" this is SH…

vue 使用nvm控制node 版本,随意切换 node 版本

1.nvm 下载安装 https://github.com/coreybutler/nvm-windows/releases 找自己版本 1.安装版本 nvm list available // 查看所有node 版本 nvm install 版本号 // 安装指定版本号2.nvm 列表展示 nvm list //展示所有版本号3.nvm 切换环境 nvm use 版本号 // 切换版本4.…

星际争霸之小霸王之小蜜蜂(四)--事件监听-让小蜜蜂动起来

目录 前言 一、监听按键并作出判断 二、持续移动 三、左右移动 总结&#xff1a; 前言 今天开始正式操控我们的小蜜蜂了&#xff0c;之前学java的时候是有一个函数监听鼠标和键盘的操作&#xff0c;我们通过传过来不同的值进行判断&#xff0c;现在来看看python是否一样的实现…

框架分析(1)-IT人必须会

框架分析&#xff08;1&#xff09;-IT人必须会 专栏介绍当今主流框架前端框架后端框架移动应用框架数据库框架测试框架 Angular关键特点和功能&#xff1a;组件化架构双向数据绑定依赖注入路由功能强大的模板语法测试友好 优缺点分析优点缺点 总结 专栏介绍 link 主要对目前市…