当前位置: 首页 > news >正文

【数据结构与算法】第4篇:算法效率衡量:时间复杂度和空间复杂度

一、写在前面

上一篇我们学会了动态内存管理,从这篇开始,要真正进入算法的世界了。

在写具体的算法之前,先要解决一个问题:怎么衡量一个算法的好坏?

两个程序员写了两个排序程序,一个跑得快,一个跑得慢,怎么定量地比较?这就引出了我们今天的主角——时间复杂度空间复杂度


二、什么是时间复杂度

时间复杂度不是指程序跑了多少秒。因为运行时间跟硬件、编译器、当前系统负载都有关系,同一段代码在不同机器上跑出来的时间可能差很多。

我们关心的是:当数据规模变大时,算法执行时间的增长趋势

2.1 大O渐进表示法

大O表示法用来描述算法的时间复杂度,它忽略常数项和低阶项,只保留最高阶。

常见的大O复杂度从低到高:

复杂度名称例子
O(1)常数阶数组按索引取值
O(log n)对数阶二分查找
O(n)线性阶遍历数组
O(n log n)线性对数阶快速排序、归并排序
O(n²)平方阶冒泡排序、选择排序
O(2ⁿ)指数阶斐波那契递归

画个图理解增长趋势(n=10时):

text

n = 10: O(1) → 1 O(log n) → 约3 O(n) → 10 O(n log n) → 约33 O(n²) → 100 O(2ⁿ) → 1024 n = 100: O(1) → 1 O(log n) → 约7 O(n) → 100 O(n log n) → 约664 O(n²) → 10000 O(2ⁿ) → 天文数字

n越大,差距越明显。这就是为什么算法设计这么重要。

2.2 大O的计算规则

规则1:只保留最高阶项

c

// 3次操作 + 1次循环(n次)+ 1次操作 // 总操作次数:n + 4 // O(n) for (int i = 0; i < n; i++) { printf("%d\n", i); // 执行n次 } printf("done\n"); // 1次

规则2:常数系数忽略

c

// 2n次操作,还是O(n) for (int i = 0; i < n; i++) { printf("%d\n", i); // n次 printf("%d\n", i); // 又是n次 }

规则3:加法法则,取最高阶

c

// 第一部分:O(n) for (int i = 0; i < n; i++) { ... } // 第二部分:O(n²) for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { ... } } // 整体:O(n + n²) = O(n²)

规则4:乘法法则,嵌套相乘

c

// 外层n次,内层m次 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { ... } } // 复杂度:O(n * m)

三、最好、最坏、平均情况

同一个算法,输入不同,执行时间可能不一样。

以顺序查找为例:

c

int search(int arr[], int n, int target) { for (int i = 0; i < n; i++) { if (arr[i] == target) return i; } return -1; }
  • 最好情况:第一个元素就是要找的,O(1)

  • 最坏情况:最后一个元素才是,或者没找到,O(n)

  • 平均情况:假设目标在每个位置概率相等,平均比较次数 n/2,还是O(n)

写算法时,我们通常关心最坏情况,因为它给出了一个上界保证。


四、空间复杂度

空间复杂度衡量算法运行时额外占用的内存(输入本身占的空间不算)。

c

// O(1) 空间复杂度,只用了几个变量 int sum(int arr[], int n) { int total = 0; // 1个变量 for (int i = 0; i < n; i++) { // i又是一个 total += arr[i]; } return total; }

c

// O(n) 空间复杂度,申请了n个int的空间 int* copyArray(int arr[], int n) { int *newArr = (int*)malloc(n * sizeof(int)); for (int i = 0; i < n; i++) { newArr[i] = arr[i]; } return newArr; }

递归的空间复杂度:每次递归调用都会在栈上分配空间,递归深度是多少,空间复杂度就是多少。


五、经典例子实战

5.1 二分查找

c

int binarySearch(int arr[], int n, int target) { int left = 0, right = n - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) return mid; else if (arr[mid] < target) left = mid + 1; else right = mid - 1; } return -1; }

分析

  • 每次循环,搜索范围缩小一半

  • n, n/2, n/4, ... 直到1

  • 需要循环 log₂n 次

时间复杂度:O(log n)

为什么log n这么快?n=100万时,log₂n ≈ 20,只需要20次比较。

5.2 斐波那契数列(递归版)

c

int fib(int n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); }

画出递归树(n=5为例):

text

fib(5) / \ fib(4) fib(3) / \ / \ fib(3) fib(2) fib(2) fib(1) / \ / \ / \ fib(2) fib(1) ... ... ...

分析

  • 每个节点分出两个子节点

  • 节点总数 ≈ 2ⁿ

  • 很多重复计算,fib(2)被算了多次

时间复杂度:O(2ⁿ)

n=50时,2⁵⁰ ≈ 1亿亿,根本跑不完。这就是指数级复杂度的可怕之处。

5.3 斐波那契数列(迭代版)

c

int fib(int n) { if (n <= 1) return n; int a = 0, b = 1, c; for (int i = 2; i <= n; i++) { c = a + b; a = b; b = c; } return b; }

分析

  • 循环执行 n-1 次

  • 每次只做常数次操作

时间复杂度:O(n)
空间复杂度:O(1)

同样的功能,从O(2ⁿ)优化到O(n),这就是好算法和烂算法的差距。


六、复杂度对比表

算法时间复杂度空间复杂度
数组按索引访问O(1)O(1)
顺序查找O(n)O(1)
二分查找O(log n)O(1)
冒泡排序O(n²)O(1)
快速排序O(n log n) 平均O(log n)
归并排序O(n log n)O(n)
斐波那契(递归)O(2ⁿ)O(n)(递归栈)
斐波那契(迭代)O(n)O(1)

七、实际开发中的取舍

复杂度分析不是纸上谈兵,实际开发中经常需要权衡:

时间换空间:比如缓存一些计算结果,用内存换时间。

c

// 不缓存:每次都重新计算 int getValue(int index) { return heavyCalculation(index); // 耗时 } // 缓存:算过的存起来 int cache[1000]; int getValue(int index) { if (cache[index] == 0) { cache[index] = heavyCalculation(index); } return cache[index]; }

空间换时间:比如哈希表,用额外内存换来O(1)的查找速度。

选择哪种,要看具体场景:

  • 内存紧张(嵌入式)→ 优先省空间

  • 响应速度要求高(Web服务)→ 优先省时间


八、小结

这一篇我们讲了:

概念要点
时间复杂度算法执行时间随数据规模的增长趋势
空间复杂度算法额外占用的内存随数据规模的增长趋势
大O表示法忽略常数和低阶项,只保留最高阶
最好/最坏/平均不同输入下的表现,通常关注最坏情况
常见复杂度O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ)

重点记住

  • 二分查找 O(log n),比 O(n) 快得多

  • 递归不总是好选择,斐波那契用递归是 O(2ⁿ),用迭代是 O(n)

  • 实际开发中,时间和空间经常需要取舍


九、思考题

  1. 下面代码的时间复杂度是多少?

c

for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { printf("%d %d\n", i, j); } }
  1. 下面代码的时间复杂度是多少?

c

int i = 1; while (i < n) { i = i * 2; }
  1. 判断对错:O(n²) 一定比 O(n) 慢。

  2. 如果要在一个有序数组中查找一个数,你会选择顺序查找还是二分查找?为什么?

欢迎在评论区讨论你的答案。

http://www.jsqmd.com/news/532869/

相关文章:

  • 问卷设计“智变”风暴:书匠策AI如何引领科研新风尚?
  • 丹青识画部署案例:海外孔子学院中文教学AI工具箱中的文化理解模块
  • PaddleOCR-VL-WEB保姆级教程:从部署到高性能调优全流程
  • 西安权际海外移民靠谱吗,口碑评价如何值得信任吗 - 工业推荐榜
  • 比迪丽LoRA模型解决403 Forbidden错误:部署与访问权限排查指南
  • Qwen3-VL:30B企业级部署:Clawdbot配置多租户隔离、模型访问权限分级、审计日志留存
  • 如何使用萤石开放平台直播大屏?功能与应用全解析
  • ESP32+MQTT阿里云+手机APP,实现智能家居控制
  • GME-Qwen2-VL-2B-Instruct部署详解:Windows系统本地开发环境配置教程
  • 成都装饰公司优选:别墅装修口碑、质量与适配性最新解析 - 深度智识库
  • 国产开源大模型2026格局:Qwen3.5与DeepSeek V3.2深度解析
  • OpenClaw高阶用法:Qwen3.5-4B-Claude多技能组合调度
  • 2026年西安权际海外移民服务排名,解析权际移民的服务质量保障与特色 - myqiye
  • 11.2版本:使用Flow3D进行高能量密度下选区激光熔化(SLM)数值模拟与计算流体动力学(...
  • 小白也能轻松上手:cv_unet_image-colorization本地AI上色工具快速入门指南
  • 分期乐购物额度回收避坑指南:3 个标准筛掉 99% 的不靠谱渠道 - 团团收购物卡回收
  • 2026年如何选择移民公司,权际移民服务特色与口碑参考 - mypinpai
  • Z-Image-Turbo-rinaiqiao-huiyewunv 前端交互实战:用Vue3构建可视化AI应用界面
  • 3步掌握神经网络可视化:PlotNeuralNet专业绘图实战指南
  • fern-wifi-cracker使用教程
  • 2026年国内热门的IPPBX软交换厂商找哪家,IP电话/IAD综合接入网关,IPPBX软交换厂家有哪些 - 品牌推荐师
  • 2026年揭秘做IBMS系统打破供应商专有生态垄断的企业 - 工业品牌热点
  • 焦耳小偷电路:高效升压转换设计解析
  • AlmaLinux 8下RealVNC自定义分辨率配置全攻略
  • 2026六大CRM系统:从线索到报表能力拆解与选型参考 - jfjfkk-
  • 论文合规双检新标杆:paperzz 查重系统,一站式破解本科毕业双重检测焦虑
  • 5大维度解锁专业音效:开源均衡器深度优化指南
  • 蚁剑免杀实战:5种PHP木马绕过360/火绒的骚操作
  • 像素幻梦应用场景:AR滤镜开发者用AI生成像素风贴纸与动态遮罩
  • 告别屎山代码!架构设计三大黄金原则 SOLID、DRY、KISS 全拆解