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

Day1 时间复杂度

一 概念

在 C++ 中,时间复杂度是衡量算法运行时间随输入规模增长的趋势的关键指标,用于评估算法的效率。它通过 大 O 表示法(Big O Notation) 描述,关注的是输入规模 n 趋近于无穷大时,算法时间增长的主导因素(忽略常数和低阶项)。

  • 输入规模(n):算法处理的数据量(如数组长度、链表节点数、矩阵维度等)。
  • 大 O 表示法:表示算法时间复杂度的上界,例如 O(n) 表示时间与输入规模 n 成线性关系。
  • 核心原则:只保留最高阶项,忽略常数因子和低阶项(如 O(2n + 3) 简化为 O(n)O(n² + n) 简化为 O(n²))。

二 常见时间复杂度类型 

1.O (1):常数时间

无论输入规模多大,算法执行时间恒定(与 n 无关)。

典型场景:

  • 数组 /vector 的随机访问(通过下标 [i])。
  • 哈希表(unordered_map)的插入、查找(平均情况)。

示例:访问数组元素 

#include <vector>int get_element(const std::vector<int>& vec, int index) {return vec[index];  // 随机访问,时间复杂度 O(1)
}

2. O (n):线性时间

时间与输入规模 n 成正比,需逐个处理每个元素。
典型场景

  • 线性枚举(遍历数组、链表等)。
  • 未排序数组的顺序查找。

示例:遍历 vector 求和

#include <vector>int sum_vector(const std::vector<int>& vec) {int sum = 0;for (int num : vec) {  // 遍历所有元素,时间复杂度 O(n)sum += num;}return sum;
}

3. O (logn):对数时间

时间随输入规模的对数增长,通常出现在分治算法中(如二分查找)。
典型场景

  • 有序数组的二分查找(std::lower_bound)。
  • 平衡二叉搜索树(如 std::setstd::map)的插入、查找。

示例:二分查找(C++ 标准库实现)

#include <vector>
#include <algorithm>  // for std::lower_bound// 查找目标值的位置(返回迭代器)
auto binary_search(const std::vector<int>& vec, int target) {return std::lower_bound(vec.begin(), vec.end(), target);// 时间复杂度 O(logn)(数组已排序)
}

4. O (nlogn):线性对数时间

时间增长趋势为 n × logn,常见于高效的排序算法。
典型场景

  • 快速排序、归并排序(平均情况)。
  • std::sort(C++ 标准库排序,基于快速排序 / 堆排序)。

示例:使用 std::sort 排序

#include <vector>
#include <algorithm>void sort_vector(std::vector<int>& vec) {std::sort(vec.begin(), vec.end());  // 平均时间复杂度 O(nlogn)
}

5.O (n²):平方时间

时间与输入规模的平方成正比,常见于双重循环的暴力算法。
典型场景

  • 冒泡排序、选择排序(最坏 / 平均情况)。
  • 二维数组的遍历(如矩阵元素逐个处理)。

示例:冒泡排序

#include <vector>void bubble_sort(std::vector<int>& vec) {int n = vec.size();for (int i = 0; i < n - 1; ++i) {for (int j = 0; j < n - i - 1; ++j) {  // 双重循环,时间复杂度 O(n²)if (vec[j] > vec[j + 1]) {std::swap(vec[j], vec[j + 1]);}}}
}

6. 其他复杂度

  • O(2ⁿ):指数时间(如斐波那契数列的递归实现,存在大量重复计算)。
  • O(n!):阶乘时间(如全排列生成)。

三、时间复杂度的分析方法 

1. 单循环:O (n)

单个循环遍历 n 次,时间复杂度为 O(n)

for (int i = 0; i < n; ++i) {// 操作(时间复杂度 O(1))
}
// 总时间复杂度:O(n)

2. 双重循环:O (n²)

外层循环 n 次,内层循环 n 次,总次数为 n × n = n²

for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {// 操作(O(1))}
}
// 总时间复杂度:O(n²)

当外层循环到第 i 次时,内层循环 i 次,总次数为 (1 + n)n / 2 = (n + n^2) / 2次。

for (int i = 0; i < n; ++i) {for (int j = 0; j <= i; ++j) {//操作(O(1))}
}
/*  i  0  1  2  3  ...  n-1j  1  1  2  3  ...  n总操作次数为等差数列:(1 + n)n / 2 = (n + n^2) / 2只保留最高阶项,忽略常数因子和低阶项,故时间复杂度O(n^2)
*/

3. 对数循环:O (logn)

每次循环将问题规模减半(如二分查找)。

int i = 1;
while (i < n) {i *= 2;  // 每次规模翻倍
}
// 循环次数为 log₂n,时间复杂度 O(logn)
int i = n * n;
while (i != 1) {i /= 2; //每次规模减少为原来的一半
}/*
t(操作次数) 0       1           2            3     ...
i          n^2   n^2 / 2     n^2 / 4      n^2 / 8  ...由上面规律可得:i = n^2 / 2^t
将该表达式与 i = 1 联立
得 t = 2log2(n)
所以时间复杂度为 O(log2(n)),记为 O(logn)
*/

4. 递归的时间复杂度

递归的时间复杂度需结合递归深度每层操作次数
示例:斐波那契数列的递归实现(低效版)

int fib(int n) {if (n <= 1) return n;return fib(n - 1) + fib(n - 2);  // 每次递归分裂为 2 个子问题
}
// 时间复杂度:O(2ⁿ)(存在大量重复计算)

四、时间复杂度的优化策略

1. 用哈希表优化查找(O (n) → O (1))

将线性查找(O (n))替换为哈希表(unordered_map)的键值查找(O (1)),以空间换时间。

示例:两数之和。在数组中找到两个元素值等于target,返回这两个元素组成的数组。

#include <vector>
#include <unordered_map>std::vector<int> two_sum(const std::vector<int>& nums, int target) {std::unordered_map<int, int> hash;  // 哈希表存储值→索引for (int i = 0; i < nums.size(); ++i) {int complement = target - nums[i];if (hash.count(complement)) {  // 查找时间 O(1)return {hash[complement], i};}hash[nums[i]] = i;  // 插入时间 O(1)}return {};
}
// 原暴力解法时间复杂度 O(n²),优化后 O(n)

2. 用排序 + 二分查找优化(O (n²) → O (nlogn))

将双重循环的暴力匹配替换为排序后二分查找。
示例:查找数组中是否存在两数之和为目标值

#include <vector>
#include <algorithm>bool has_two_sum(std::vector<int>& nums, int target) {std::sort(nums.begin(), nums.end());  // 排序 O(nlogn)for (int num : nums) {int complement = target - num;// 二分查找 complement,时间 O(logn)if (std::binary_search(nums.begin(), nums.end(), complement)) {return true;}}return false;
}
// 原暴力解法 O(n²),优化后 O(nlogn)

3. 避免重复计算(O (2ⁿ) → O (n))

通过动态规划或记忆化搜索,消除递归中的重复子问题。
示例:斐波那契数列的动态规划实现

#include <vector>int fib(int n) {if (n <= 1) return n;std::vector<int> dp(n + 1);  // 存储已计算的结果dp[0] = 0;dp[1] = 1;for (int i = 2; i <= n; ++i) {dp[i] = dp[i - 1] + dp[i - 2];  // 避免重复计算,时间 O(n)}return dp[n];
}
// 原递归解法 O(2ⁿ),优化后 O(n)

五 总结

时间复杂度反映算法运行时间随输入规模增长的趋势,不同复杂度的增长速度从低到高排列如下:

O(1) < O(logn) < O(n) < O(nlogn) < O(n²)

时间复杂度是评估算法效率的核心指标。在 C++ 中,通过分析循环嵌套、递归深度或操作次数,可以确定算法的时间复杂度。实际编码时,应优先选择低时间复杂度的算法(如 O (nlogn) 优于 O (n²)),并利用哈希表、排序 + 二分等优化手段降低复杂度。同时,结合数据结构的特性(如unordered_map的 O (1) 查找),可以显著提升程序性能。

相关文章:

  • 【深度学习-Day 10】机器学习基石:从零入门线性回归与逻辑回归
  • 云共享虚拟主机具体是指什么?
  • “追光”植物背后的故事(二)
  • SpringBoot--springboot简述及快速入门
  • 基于 PLC 的轮式服务机器人研究
  • 医疗实时操作系统方案:手术机器人的微秒级运动控制
  • 【Hot 100】208. 实现 Trie (前缀树)
  • 基于C#+MySQL实现(WinForm)企业设备使用信息管理系统
  • niushop单商户V5多门店版V5.5.0全插件+商品称重、商家手机端+搭建环境教程
  • 从数据中台到数据飞轮:数字化转型的演进之路
  • python如何做人脸识别
  • Docker与PostgreSQL
  • 国联股份卫多多与七腾机器人签署战略合作协议
  • 基于Spring Boot+Layui构建企业级电子招投标系统实战指南
  • 工业巡检机器人 —— 机器人市场的新兴增长引擎
  • OpenTiny icons——超轻量的CSS图标库,引领图标库新风向
  • Git 第二讲---提高篇 git的分支管理
  • AD 飞线的显示与隐藏
  • 图论part09dijkstra算法
  • 【时时三省】(C语言基础)使用字符串处理函数
  • 美政府以拨款为要挟胁迫各州服从移民政策,20个州联合起诉
  • GDP逼近五千亿,向海图强,对接京津,沧州剑指沿海经济强市
  • 受美关税影响,本田预计新财年净利下降七成,并推迟加拿大建厂计划
  • 受贿3501万余元,中石油原董事长王宜林一审被判13年
  • 真人秀《幸存者》百万美元奖金,25年间“缩水”近一半
  • 牟海松任国家信访局副局长