算法与数据结构——时间复杂度

news/2024/9/18 3:46:03/文章来源:https://www.cnblogs.com/1873cy/p/18367508

时间复杂度

运行时间可以直观且准确地反映算法的效率。要准确预估一段代码的运行时间,应该进行如下操作。

  • 确定运行平台,包括硬件配置、编程语言、系统环境等,这些因素都会影响代码的运行效率。
  • 评估各种计算操作的运行时间,例如加法操作需要1ns,乘法操作需要10ns,打印操作需要5ns等。
  • 统计代码中所有的计算操作,并将所有操作的执行时间求和,从而得到运行时间。
// 在某运行平台下
void algorithm(int n) {int a = 2; // 1 nsa = a + 1; // 1 nsa = a * 2; // 10 ns// 循环 n 次for (int i = 0; i < n; i++) { // 1 ns ,每轮都要执行 i++std::cout << 0 << std::endl; // 5 ns}
}

根据以上方法,可以得到算法的运行时间为(6n+12)ns

但时间上,统计算法的运行时间既不合理也不现实。首先,我们不希望将预估时间和运行平台绑定,因为算法需要在各种不同的平台上运行。其次我们很难获知每种操作的运行时间,这给预估过程带来了极大的难度。

统计时间增长趋势

时间复杂度分析统计的不是算法运行时间,而是算法运行时间随着数据量变大时的增长趋势

“时间增长趋势”这个概念比较抽象,我们通过一个例子来加以理解。假设输入数据大小为n,给定三个算法A、B、C:

// 算法 A 的时间复杂度:常数阶
void algorithm_A(int n) {cout << 0 << endl;
}
// 算法 B 的时间复杂度:线性阶
void algorithm_B(int n) {for (int i = 0; i < n; i++) {cout << 0 << endl;}
}
// 算法 C 的时间复杂度:常数阶
void algorithm_C(int n) {for (int i = 0; i < 1000000; i++) {cout << 0 << endl;}
}
  • 算法A只有一个打印操作,算法运行时间不随着n增大而增大。我们称此算法的时间复杂度为“常数阶”。
  • 算法B中的打印操作需要循环n次,算法运行时间随着n增大而线性增加。此算法的时间复杂度被称为“线性阶”。
  • 算法C中的打印操作需要循环1000000次,虽然运行时间很长,但是它与输入数据大小n无关。因此C的时间复杂度与A相同,均为“常数阶”。

函数渐近上界

给定一个输入大小为n的函数:

void algorithm(int n) {int a = 2; // +1 a = a + 1; // +1 a = a * 2; // +1// 循环 n 次for (int i = 0; i < n; i++) { // +1  ,每轮都要执行 i++std::cout << 0 << std::endl; // +1}
}

设算法的操作数量是一个关于输入数据大小n的函数,记为 𝑇(𝑛),则以上函数的操作数量为:

 𝑇(𝑛) = 3 + 2𝑛

 𝑇(𝑛) 是一次函数,说明其运行时间的增长趋势是线性的,因此它的时间复杂度是线性阶。

我们将线性阶的时间复杂度记为  𝑂(𝑛) ,这个数学符号称为大O记号,表示函数 𝑇(𝑛) 的渐近上界(asymptotic upper bound)。

时间复杂度分析本质上是计算“操作数量𝑇(𝑛)”的渐近上界,它具有明确的数学定义。

推算方法

总体上分为两步:首先统计操作数量,然后判断渐近上界。

第一步:统计操作数量

针对代码,逐行从上到下计算即可。操作数量𝑇(𝑛)中的各种系数、常数项都可以忽略。根据此原则,可总结出一下计数简化技巧。

  • 忽略𝑇(𝑛)中的常数项
  • 省略所有系数
  • 嵌套循环时使用乘法。总操作数量等于外层循环和内层循环操作数量之积。
void algorithm(int n) {int a = 1; // +0(技巧 1)a = a + n; // +0(技巧 1)// +n(技巧 2)for (int i = 0; i < 5 * n + 1; i++) {cout << 0 << endl;}// +n*n(技巧 3)for (int i = 0; i < 2 * n; i++) {for (int j = 0; j < n + 1; j++) {cout << 0 << endl;}}
}

 以下公式展示了使用上述技巧前后的统计结果,两者推算出的时间复杂度都为 𝑂(𝑛2
) 。
𝑇(𝑛) = 2𝑛(𝑛 + 1) + (5𝑛 + 1) + 2 完整统计 (‑.‑|||)
= 2n2 + 7𝑛 + 3
𝑇(𝑛) = 𝑛2 + 𝑛 偷懒统计 (o.O) 

第二步:判断渐近上界

时间复杂度由𝑇(𝑛)中最高阶的项来决定。这是因为在n趋于无穷大时,最高阶的项将发挥主导作用,其他项的影响都可以忽略。

下表展示了一些例子,其中一些夸张的值是为了强调“系数无法撼动阶数”这一结论。

常见类型

设输入数据大小为n,常见的时间复杂度类型如图所示:

常数阶 𝑂(1) 

常数阶的操作数量与输入数据大小n无关,即不随着n的变化而变化。

线性阶 𝑂(n) 

线性阶的操作数量相对于数据大小n以线性级别增长。通常出现在单层循环中。遍历数组和遍历链表等操作的时间复杂度均为 𝑂(𝑛) ,其中n为数组或链表的长度。

平方阶 𝑂(n2

平方阶通常出现在嵌套循环中,外层循环和内层循环的时间复杂度都为 𝑂(𝑛) ,因此总体的时间复杂度为 𝑂(𝑛2) 。

以冒泡排序为例,外层循环执行n-1次,内层循环执行n-1、n-2、n-3、...、2、1次,平均n/2次,因此时间复杂度为 𝑂((𝑛 − 1)𝑛/2) = 𝑂(𝑛2)

/*平方阶-冒泡排序*/
int bubbleSort(vector<int> &nums){/*for (int i = 0; i < nums.size(); i++){for (int j = i + 1; j < nums.size(); j++){if (nums[i] > nums[j]){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}}}*/for (int i = 0; i < nums.size(); i++){for (int j = 0; j < nums.size() - i - 1; j++){if (nums[j] > nums[j + 1]){int temp = nums[j];nums[j] = nums[j + 1];nums[j + 1] = temp;}}}
}

指数阶 𝑂(2n

生物学的“细胞分裂”是指数阶增长的典型例子:初识状态为1个细胞,分裂一轮后变为4个,以此类推,n轮分裂后有2n个细胞。

在实际算法中,指数阶常出现于递归函数中。

 

/* 指数阶(循环实现) */
int exponential(int n) {int count = 0, base = 1;// 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)for (int i = 0; i < n; i++) {for (int j = 0; j < base; j++) {count++;}base *= 2;}// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1return count;
}
/* 指数阶(递归实现) */
int expRecur(int n) {if (n == 1)return 1;return expRecur(n - 1) + expRecur(n - 1) + 1;
}

指数阶增长非常迅速,在穷举(暴力搜索、回溯等)中比较常见。对于数据规模较大的问题,指数阶是不可接受的,通常需要使用动态规划或贪心算法等来解决。

对数阶 𝑂(log 𝑛) 

与指数阶相反,对数阶反映了“每轮缩减到一半”的情况。设输入数据大小为n,由于每轮缩减到一半,因此循环次数是log2n

与指数阶类似,对数阶也常出现于递归函数中。以下代码形成了一棵高度为log2n的递归树:

/* 对数阶(递归实现) */
int logRecur(int n) {if (n <= 1)return 0;return logRecur(n / 2) + 1;
}

对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。

线性对数阶 𝑂(𝑛 log 𝑛) 

线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别是𝑂(log 𝑛) 和 𝑂(𝑛)

如图展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为n,树共有 log2𝑛 + 1 层,因此时间复杂度为𝑂(𝑛 log 𝑛) 。

主流排序算法的时间复杂度通常为𝑂(𝑛 log 𝑛),例如快速排序归并排序堆排序等。

阶乘阶𝑂(𝑛!)

阶乘对应数学上的“全排列”问题。给定n个互不重复的元素,求其所有可能的排列方案,方案数量为:

𝑛! = 𝑛 × (𝑛 − 1) × (𝑛 − 2) × ⋯ × 2 × 1 

阶乘通常使用递归实现:

/* 阶乘阶(递归实现) */
int factorialRecur(int n) {if (n == 0)return 1;int count = 0;// 从 1 个分裂出 n 个for (int i = 0; i < n; i++) {count += factorialRecur(n - 1);}return count;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法与数据结构——了解复杂度(迭代与递归)

复杂度分析 算法效率评估 在算法设计中,我们追求以下两个层面的目标。找到问题解法:算法需要再规定的输入范围内可靠地求得问题的正确解 寻求最优解法:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。也就是说,在能够解决问题的前提下,算法效率已经成为衡量算…

算法与数据结构——了解复杂度

复杂度分析 算法效率评估 在算法设计中,我们追求以下两个层面的目标。找到问题解法:算法需要再规定的输入范围内可靠地求得问题的正确解 寻求最优解法:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。也就是说,在能够解决问题的前提下,算法效率已经成为衡量算…

算法与数据结构——复杂度分析

复杂度分析 算法效率评估 在算法设计中,我们追求以下两个层面的目标。找到问题解法:算法需要再规定的输入范围内可靠地求得问题的正确解 寻求最优解法:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。也就是说,在能够解决问题的前提下,算法效率已经成为衡量算…

AI大模型快速生成题库-助力业务人效提升10+倍

一 现状问题 1、培训考核涉及的文件数量较多 当前,京东航空公司维修部门面临着人员规模的快速增长和持续的培训需求。根据民航局的规定,维修培训必须确保所有维修人员都能够完成对飞机维修相关文件的学习,这包括维修方案、维修工程管理手册、工作程序手册等共计12本手册以及…

存储系列之 Linux ext2 概述

来自:https://www.cnblogs.com/orange-CC/p/12673052.html存储系列之 Linux ext2 概述引言:学习经典永不过时。我们之前介绍过存储介质主要是磁盘,先介绍过物理的,后又介绍了虚拟的。保存在磁盘上的信息一般采用文件(file)为单位,磁盘上的文件必须是持久的,同时文件是通…

存储系列之 从ext2到ext3、ext4 的变化与区别

来自:https://www.cnblogs.com/orange-CC/p/12673073.html存储系列之 从ext2到ext3、ext4 的变化与区别引言:ext3 和 ext4 对 ext2 进行了增强,但是其核心设计并没有发生变化。所以建议先查看上上篇的《存储系列之 Linux ext2 概述 》,有了ext2的基础,看这篇就是so easy了…

【YashanDB知识库】生成迁移报告失败,报错未知类型错误异常:

【标题】YMP迁移 【问题分类】迁移报告 【关键字】迁移报告、未知类型错误异常 【问题描述】下载迁移报告时报错“未知类型错误异常:”,一长串英文日志报错:【问题原因分析】java版本不对,ymp仅支持java 8和11版本,用户环境用的21版本。【解决/规避方法】将java版本更新为8…

Visual Studio 2013 自定义动态库dll文件lib存放路径

前言全局说明Visual Studio 2013 自定义lib存放路径一、说明 环境: Windows 7 旗舰版 Visual Studio 2013二、设置说明 在一个功能比较全的项目中,有可能会引入第三方库来完成某些功能, 为了让目录结构、文件,清晰,会将引入的dll文件,放置到一个独立目录里。 这样方便管理…

7.路由器配置及使用

9.1 路由器的结构内存只读内存随机存储器ARP:广播信息 非易失随机存储器闪存9.2 路由器的工作原理9.3 路由表 注意,分两种路由表 路由器的路由表(基于端口的)三层交换机的路由表(基于vlan的)9.4 路由器的工作模式9-5 路由器的基本配置及常见命令 路由的配置方式*重要9-6 …

dedecms 两个常见漏洞的复现

dedecms系统的部分复现,还有更多的漏洞没有去复现和发现。侵权声明 本文章中的所有内容(包括但不限于文字、图像和其他媒体)仅供教育和参考目的。如果在本文章中使用了任何受版权保护的材料,我们满怀敬意地承认该内容的版权归原作者所有。 如果您是版权持有人,并且认为您的…

过滤器与拦截器

过滤器 与拦截器 参考https://www.cnblogs.com/Black-Ice/p/16248535.html过滤器 Filter Filter 基本介绍 过滤器 Filter 是 Sun 公司在 Servlet 2.3 规范中添加的新功能,其作用是对客户端发送给 Servlet 的请求以及对 Servlet 返回给客户端的响应做一些定制化的处理,例如校验…

独立站是什么?独立站的优势是什么?为什么要做独立站?一键三问

独立站是指一个完全独立的网站,由公司自主搭建和运营,包括独立的服务器、网站程序和单独的域名等等,完全不依赖于任何第三方平台。它最初用于区分与亚马逊、eBay、速卖通等各种第三方电商平台的区别。市面上有三种建站系统:全自主开发、基于开源软件建设的独立站和基于SaaS…