算法复杂度:那些神秘符号背后的故事

news2026/5/25 9:04:34
算法复杂度那些神秘符号背后的故事 开篇为什么需要这套数学语言想象一下你要向朋友描述不同汽车的油耗❌没有统一标准“我的车挺省油的”“他的车特别费油”“那辆车还行吧”✅有统一标准“我的车百公里6升”“他的车百公里12升”“那辆车百公里8升”算法复杂度就是计算机世界的油耗标准让我们能客观比较不同算法的效率 第一部分为什么要发明 O 符号 历史背景1976年计算机科学家Donald Knuth高德纳在他的经典著作《计算机程序设计艺术》中正式引入了大O符号。为什么要发明它问题1同样的算法在不同电脑上速度不一样同一程序运行时间 - 在老电脑上10秒 - 在新电脑上1秒 - 在超级电脑上0.1秒 怎么公平比较问题2数据量不同运行时间也不同排序算法A - 排10个数0.001秒 - 排1000个数0.5秒 排序算法B - 排10个数0.002秒 - 排1000个数0.3秒 哪个算法更好✅ 解决方案关注增长趋势而不是具体时间大O符号的核心思想不关心具体跑多快只关心数据变多时速度会怎样变化 第二部分O、n、log 是怎么来的O- “Order of”数量级O 来自数学中的 “Order”意思是级别或量级正式名称Big O Notation大O记号法 学名渐近上界Asymptotic Upper Bound 简单说O 告诉你算法的天花板有多高为什么用 O 而不用其他字母来自德语 “Ordnung”秩序、等级数学家 Paul Bachmann 在1894年首次使用后来被计算机科学广泛采用n- “number”数量n 代表输入数据的规模为什么用小写 n - 数学传统用 n 表示自然数或计数 - 英文单词number数量、n itemsn个项目 - 约定俗成所有教材都用 n形成标准实际含义排序算法n 要排序的元素个数 搜索算法n 要搜索的范围大小 图算法n 节点或边的数量log- “对数”Logarithmlog 是数学家 John Napier 在1614年发明的 为什么叫对数英文Logarithm 拆解logos比例 arithmos数字 中文翻译对数 对应的指数 直观理解 loglog 回答这个问题需要乘以/除以多少次才能达到目标// log₂(8) 3 的意思2×2×28乘了3次 或者8÷2÷2÷21除了3次// 在计算机中常用的是 log₂以2为底因为计算机喜欢二分法每次排除一半 为什么排序算法中经常出现 log n因为聪明的算法都在用分治法Divide and Conquer例子从1000个数中找一个数 ❌ 笨方法O(n)逐个检查最多查1000次 ✅ 聪明方法O(log n) 第1次排除500个剩500个 第2次排除250个剩250个 第3次排除125个剩125个 ... 只需 log₂(1000) ≈ 10 次 第三部分这些公式是怎么计算出来的1️⃣O(1)- 常数时间怎么看出来的functiongetFirst(arr){returnarr[0];// 只有1步操作}// 不管数组多大都是1步 → O(1)设计原理操作次数不随 n 变化数学表达f(n) 1简化为O(1)2️⃣O(n)- 线性时间怎么看出来的functionsum(arr){lettotal0;for(leti0;iarr.length;i){// 循环n次totalarr[i];}returntotal;}// 数组有n个元素就要加n次 → O(n)设计原理操作次数 n数学表达f(n) n简化为O(n)3️⃣O(n²)- 平方时间怎么看出来的functionbubbleSort(arr){for(leti0;iarr.length;i){// 外层n次for(letj0;jarr.length-1;j){// 内层n次if(arr[j]arr[j1]){swap(arr[j],arr[j1]);}}}}// 嵌套循环n × n n² → O(n²)设计原理操作次数 n × n n²数学表达f(n) n²简化为O(n²)4️⃣O(log n)- 对数时间怎么看出来的functionbinarySearch(arr,target){letleft0,rightarr.length-1;while(leftright){letmidMath.floor((leftright)/2);if(arr[mid]target){returnmid;}elseif(arr[mid]target){leftmid1;// 只在右半部分找}else{rightmid-1;// 只在左半部分找}// 每次循环搜索范围减半}}// 每次排除一半 → O(log n)推导过程假设需要 k 次操作 第1次n ÷ 2 第2次n ÷ 4 第3次n ÷ 8 ... 第k次n ÷ 2^k 1 解方程n ÷ 2^k 1 得到2^k n 取对数k log₂(n) 所以是 O(log n)5️⃣O(n log n)- 线性对数时间怎么看出来的以归并排序为例functionmergeSort(arr){// 第1步拆分递归 log n 层if(arr.length1)returnarr;letmidMath.floor(arr.length/2);letleftmergeSort(arr.slice(0,mid));// 递归左半letrightmergeSort(arr.slice(mid));// 递归右半// 第2步合并每层需要 n 次操作returnmerge(left,right);}// 分析// - 递归深度log n 层每次减半// - 每层合并n 次操作所有元素都要参与// - 总操作n × log n → O(n log n)可视化理解原始数组: [5, 2, 8, 1, 9, 3, 7, 4] 第1层拆分 ← 第1层 [5,2,8,1] [9,3,7,4] 第2层拆分 ← 第2层 [5,2] [8,1] [9,3] [7,4] 第3层拆分 ← 第3层 [5] [2] [8] [1] [9] [3] [7] [4] 第3层合并 ← 合并8个元素需要n次操作 [2,5] [1,8] [3,9] [4,7] 第2层合并 ← 合并4组需要n次操作 [1,2,5,8] [3,4,7,9] 第1层合并 ← 合并2组需要n次操作 [1,2,3,4,5,7,8,9] 总共 - 拆分了 log₂(8) 3 层 - 每层合并需要 n 8 次操作 - 总计8 × 3 24 次 ≈ n log n数学推导设 T(n) 为排序n个元素的时间 T(n) 2T(n/2) n 解释 - 2T(n/2)递归处理左右两半 - n合并两个有序数组需要n次操作 用主定理Master Theorem求解 T(n) O(n log n)6️⃣快速排序的复杂度分析✅ 最好/平均情况O(n log n)理想情况每次选的队长都在中间 [5, 2, 8, 1, 9, 3, 7, 4] 选队长5 [2, 1, 3, 4] [5] [8, 9, 7] ↓ ↓ 继续分 继续分 递归深度log n 层 每层处理n 个元素 总计n log n❌ 最坏情况O(n²)最坏情况每次选的队长都是最大或最小 [1, 2, 3, 4, 5, 6, 7, 8] 已经有序 选队长1 [] [1] [2, 3, 4, 5, 6, 7, 8] ↓ 选队长2 [] [2] [3, 4, 5, 6, 7, 8] ↓ 继续... 递归深度n 层退化成链表 每层处理n, n-1, n-2, ..., 1 个元素 总计n (n-1) (n-2) ... 1 n(n1)/2 ≈ n² 第四部分为什么这样设计 设计原则1忽略常数因子算法A需要 3n 次操作 算法B需要 100n 次操作 理论上都是 O(n)但实际差距很大啊 ✅ 大O的设计哲学 - 关注增长趋势不是绝对速度 - 常数因子取决于硬件、编译器优化等 - n 很大时n 和 n² 的差距远大于 3n 和 100n 的差距举例当 n 1,000,000 时 - 3n 3,000,000 - 100n 100,000,000 - n² 1,000,000,000,000 看到了吗n² 比 100n 大了1万倍 所以常数因子不重要关键是 n 还是 n² 设计原则2只看最高阶项某个算法的操作次数 f(n) 3n² 100n 50 为什么简化为 O(n²) 因为 n 很大时 - n 1000 3n² 3,000,000 100n 100,000 50 50 3n² 占了99.9%低阶项可以忽略数学依据lim(n→∞) (3n² 100n 50) / n² 3 当 n 趋向无穷大时3n² 主导了整个表达式 设计原则3区分最好、平均、最坏情况为什么要分析三种情况 快速排序例子 - 最好O(n log n) ← 数据随机分布 - 平均O(n log n) ← 大多数情况 - 最坏O(n²) ← 数据已有序且选第一个为队长 实际意义 - 最好情况了解算法潜力 - 平均情况日常使用的预期性能 - 最坏情况保证不会慢于这个界限 设计原则4稳定性的重要性什么是稳定性 相同值的元素排序后保持原来的相对顺序 例子按成绩排序学生 原始[(小明,85), (小红,90), (小刚,85)] 稳定排序结果 [(小明,85), (小刚,85), (小红,90)] ↑小明还在小刚前面保持原顺序 不稳定排序可能 [(小刚,85), (小明,85), (小红,90)] ↑顺序变了 为什么重要 多条件排序时先按班级再按成绩稳定性保证结果正确 设计原则5空间复杂度的权衡时间复杂度算法要多快 空间复杂度算法要多大内存 常见权衡 - 快速排序时间O(n log n)空间O(log n) ← 省空间 - 归并排序时间O(n log n)空间O(n) ← 费空间但稳定 - 插入排序时间O(n²)空间O(1) ← 最省空间 没有完美的算法只有适合场景的选择 第五部分排序算法复杂度对比表算法最好平均最坏空间稳定设计思想快速排序O(n log n)O(n log n)O(n²)O(log n)❌分治法选队长分区归并排序O(n log n)O(n log n)O(n log n)O(n)✅分治法拆分后合并插入排序O(n)O(n²)O(n²)O(1)✅逐个插入已排序序列TimSortO(n)O(n log n)O(n log n)O(n)✅自适应混合策略 第六部分如何自己推导复杂度 步骤1数循环次数// 例1单层循环for(leti0;in;i){console.log(i);}// 执行n次 → O(n)// 例2双层循环for(leti0;in;i){for(letj0;jn;j){console.log(i,j);}}// 执行n×n次 → O(n²) 步骤2看递归深度// 例1二分查找functionbinarySearch(n){if(n1)return;binarySearch(n/2);// 每次减半}// 递归深度log n → O(log n)// 例2归并排序functionmergeSort(n){if(n1)return;mergeSort(n/2);// 左半mergeSort(n/2);// 右半merge(n);// 合并n个元素}// 深度log n每层工作n → O(n log n) 步骤3用主定理Master Theorem适用于分治算法的复杂度计算形式T(n) aT(n/b) f(n) 其中 - a子问题数量 - n/b每个子问题的规模 - f(n)合并操作的代价 常见情况 1. T(n) 2T(n/2) n → O(n log n) 归并排序 2. T(n) 2T(n/2) O(1) → O(n) 遍历二叉树 3. T(n) T(n/2) O(1) → O(log n) 二分查找 第七部分实际应用建议 如何选择排序算法// JavaScriptarr.sort();// 内置排序通常是TimSort或快速排序// Pythonsorted(arr)# TimSort// JavaArrays.sort(arr);// 双轴快速排序基本类型或 TimSort对象// Cstd::sort(arr.begin(),arr.end());// introsort混合排序⚠️ 常见陷阱// ❌ 错误认为 O(n²) 一定比 O(n log n) 慢// 当 n 很小时常数因子更重要if(n50){insertionSort(arr);// O(n²) 但常数小实际更快}else{quickSort(arr);// O(n log n)}// ✅ 正确根据数据特点选择if(isNearlySorted(arr)){insertionSort(arr);// 接近有序时 O(n)}else{mergeSort(arr);// 一般情况 O(n log n)} 总结记住这些就够了 核心概念O 是什么算法效率的度量衡关注增长趋势不是绝对时间n 是什么输入数据的规模要处理的元素个数log n 是什么每次排除一半的操作次数分治法的标志为什么这样设计忽略硬件差异关注可扩展性提供理论上限 实用口诀O(1) → 一步到位雷打不动 O(log n) → 每次减半聪明绝顶 O(n) → 一个不少逐个检查 O(n log n)→ 分治合并排序标配 O(n²) → 双重循环小心变慢 O(2ⁿ) → 能不用就不用太慢了 延伸阅读《算法导论》Introduction to Algorithms- CLRS《计算机程序设计艺术》- Donald Knuth在线可视化工具https://visualgo.net/en/sorting现在你知道了这些看似神秘的符号其实是计算机科学家为了让算法比较更公平、更科学而设计的通用语言。下次看到 O(n log n)你就知道这是在说“这个算法很聪明用了分治法每个元素都要做对数次操作”

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…