常见 时间复杂度计算

时间复杂度描述算法运行时间随输入规模增长的变化趋势,用大O记号表示算法的最坏或平均情况。


1. 时间复杂度核心概念

4种常见复杂度
复制代码
O(1): 常数时间,与输入规模无关
O(log n): 对数时间,输入翻倍,操作+1
O(n): 线性时间,输入翻倍,时间翻倍
O(n²): 平方时间,输入翻倍,时间×4
计算规则
复制代码
// 1. 只保留最高阶项
O(3n² + 2n + 1) = O(n²)

// 2. 忽略常数系数
O(2n) = O(n)
O(100) = O(1)

// 3. 考虑最坏情况
// 比如查找可能在第一个或最后一个找到

2. O(1) 常数时间

定义

操作时间不随输入规模变化

vector 随机访问,这里的"随机"是任意、随时、不按顺序的意思。
cpp 复制代码
std::vector<int> vec = {1, 2, 3, 4, 5, ..., 100 0000};
int value = vec[500000];  // 直接计算地址访问

// 计算过程:
地址 = 起始地址 + 索引 × 元素大小
地址 = 0x1000 + 500000 × 4 = 0x1E8480
// 一次计算,一次内存访问

为什么是 O(1)

  • 数组连续存储

  • 地址 = 基址 + 索引×元素大小

  • 计算复杂度与 n 无关

其他 O(1) 操作
cpp 复制代码
// 1. 哈希表查找(平均)
std::unordered_map<int, string> m;
auto it = m.find(key);  // 平均 O(1)

// 2. 栈操作
stack.top();    // O(1)
stack.empty();  // O(1)
栈操作 O(1) 是因为只操作栈顶

// 3. 链表插入(特定位置)
auto it = list.begin();
list.insert(it, value);  // 已知位置,O(1)

3. O(n) 线性时间

定义

操作时间与输入规模成正比

vector 中间插入
复制代码
std::vector<int> vec = {1, 2, 3, 4, 5};
// 在位置2插入99
vec.insert(vec.begin() + 2, 99);
// 需要移动元素3,4,5

移动元素过程

复制代码
原始: [1, 2, 3, 4, 5]
         ↑ 插入位置
移动: 3→4, 4→5, 5→新位置
结果: [1, 2, 99, 3, 4, 5]
// 移动了 n-2 个元素

计算复杂度

复制代码
最坏情况:在开头插入
移动 n 个元素 → O(n)

平均情况:在中间插入
移动 n/2 个元素 → O(n)
顺序查找
复制代码
// 在无序数组中查找
bool linear_search(const vector<int>& v, int target) {
    for (int i = 0; i < v.size(); ++i) {  // 循环 n 次
        if (v[i] == target) return true;
    }
    return false;
}
// 遍历所有元素,最多 n 次比较

数学表达

复制代码
比较次数:1, 2, 3, ..., n
平均:n/2
最坏:n
→ O(n)

4. O(log n) 对数时间

定义

每步将问题规模减半

二分查找
复制代码
vector<int> v = {1, 3, 5, 7, 9, 11, 13};  // 已排序
int target = 7;

// 查找过程:
[1, 3, 5, 7, 9, 11, 13]  mid=7 ✓
         ↑
找到!比较1次

查找 1

复制代码
[1, 3, 5, 7, 9, 11, 13]  mid=7 > 1
[1, 3, 5]                mid=3 > 1
[1]                      mid=1 ✓
// 比较3次

数学推导

复制代码
每次比较后,范围减半
n → n/2 → n/4 → ... → 1

需要 k 次比较:
n / 2^k = 1
2^k = n
k = log₂(n)
→ O(log n)

实际计算

复制代码
n=8:  log₂8 = 3次
n=1024: log₂1024 = 10次
n=1000000: log₂1000000 ≈ 20次
// 输入百万,仅需20次比较!

5. O(n log n) 对数线性时间

定义

执行 n 次 O(log n) 操作

快速排序:每个递归层级处理的元素总数是 Θ(n),递归深度平均是 Θ(log n)
复制代码
vector<int> v = {5, 2, 8, 1, 9, 3};
std::sort(v.begin(), v.end());

分治过程

复制代码
原始: [5, 2, 8, 1, 9, 3]
1. 分区: [2, 1, 3] 5 [8, 9]  // 比较5次
2. 递归左: [1, 2, 3]         // 比较2次
3. 递归右: [8, 9]           // 比较1次
总计: 5+2+1 = 8次比较

复杂度分析

每个递归层级处理的元素总数是 Θ(n)的原因:


6. O(n²) 平方时间

定义

操作时间与输入规模的平方成正比

冒泡排序
复制代码
void bubble_sort(vector<int>& v) {
    int n = v.size();
    for (int i = 0; i < n-1; ++i) {        // n-1 次
        for (int j = 0; j < n-i-1; ++j) {  // 平均 n/2 次
            if (v[j] > v[j+1]) {
                swap(v[j], v[j+1]);
            }
        }
    }
}

比较次数

复制代码
外层循环: i = 0 到 n-2 → n-1 次
内层循环: 当 i=0 时比较 n-1 次
         i=1 时比较 n-2 次
         ...
         i=n-2 时比较 1 次

总计比较:
(n-1) + (n-2) + ... + 1
= n(n-1)/2
= (n² - n)/2
→ O(n²)

实际计算

复制代码
n=10: 比较 45 次
n=100: 比较 4950 次
n=1000: 比较 499500 次 ≈ 50万

7. vector /list 操作复杂度分析

vector

复制代码
// 1. 末尾添加
vec.push_back(value);  // 平摊 O(1)
// 偶尔扩容 O(n),但平摊到每次是 O(1)

// 2. 随机访问
vec[i];  // O(1)

// 3. 中间插入
vec.insert(pos, value);  // O(n)

// 4. 查找
auto it = find(vec.begin(), vec.end(), x);  // O(n)
list 操作
复制代码
// 1. 插入(已知位置)
list.insert(it, value);  // O(1)
// 但找到位置可能需要 O(n)

// 2. 随机访问
// 没有索引操作,必须遍历
auto it = std::next(list.begin(), k);  // O(k)

// 3. 查找
auto it = find(list.begin(), list.end(), x);  // O(n)

8. 复杂度对比表

复杂度 n=10 n=100 n=1000 n=10000 例子
O(1) 1 1 1 1 数组访问
O(log n) 3.3 6.6 10 13.3 二分查找
O(√n) 3.2 10 31.6 100 质数检查
O(n) 10 100 1000 10000 线性查找
O(n log n) 33 664 9966 132877 快速排序
O(n²) 100 10000 10⁶ 10⁸ 冒泡排序
O(2ⁿ) 1024 1.3×10³⁰ 巨大 天文数字 汉诺塔

9. 计算代码复杂度的总和

规则1:顺序相加
复制代码
void example(vector<int>& v) {
    // 步骤1: O(n)
    for (int i = 0; i < v.size(); ++i) {  // n 次
        process1(v[i]);
    }
    
    // 步骤2: O(n)
    for (int i = 0; i < v.size(); ++i) {  // n 次
        process2(v[i]);
    }
    // 总计: O(n) + O(n) = O(2n) = O(n)
}
规则2:嵌套相乘
复制代码
void nested_example(vector<int>& v) {
    int n = v.size();
    // 外层: n 次
    for (int i = 0; i < n; ++i) {  
        // 内层: n 次
        for (int j = 0; j < n; ++j) {  
            process(v[i], v[j]);  // 执行 n×n 次
        }
    }
    // 总计: O(n²)
}
规则3:递归分析
复制代码
int binary_search(const vector<int>& v, int l, int r, int target) {
    if (l > r) return -1;
    int mid = l + (r - l) / 2;
    
    if (v[mid] == target) return mid;
    if (v[mid] > target) 
        return binary_search(v, l, mid-1, target);  // 问题减半
    else
        return binary_search(v, mid+1, r, target);
}
// 递归深度: O(log n)
// 每次递归: O(1)
// 总计: O(log n)

10. 选择 vector vs list

复制代码
// 场景1:频繁随机访问
std::vector<int> vec;  // ✅ O(1) 访问
// 场景2:频繁中间插入/删除
std::list<int> lst;    // ✅ O(1) 插入

// 但注意:list 找到位置需要 O(n)
// vector 插入需要 O(n)
// 需权衡

总结

"时间复杂度用大O记号表示算法最坏情况下的增长趋势:O(1) 常数时间(数组访问),O(log n) 对数时间(二分查找),O(n) 线性时间(顺序查找),O(n log n) 对数线性时间(快速排序),O(n²) 平方时间(冒泡排序)。计算时保留最高阶、忽略常数系数。"

关键记忆

  1. O(1):一次操作,与n无关

  2. O(log n):每步问题规模减半

  3. O(n):遍历一次

  4. O(n log n):分治法典型复杂度

  5. O(n²):双层循环

  6. 计算:分析循环嵌套和递归深度

相关推荐
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 48. 旋转图像 | C++ 矩阵变换题解
c++·leetcode·矩阵
不爱吃炸鸡柳3 小时前
单链表专题(完整代码版)
数据结构·算法·链表
Ricky_Theseus3 小时前
C++右值引用
java·开发语言·c++
CylMK3 小时前
题解:AT_abc382_d [ABC382D] Keep Distance
算法
Dfreedom.3 小时前
计算机视觉全景图
人工智能·算法·计算机视觉·图像算法
吴梓穆4 小时前
UE5 c++ 常用方法
java·c++·ue5
云栖梦泽4 小时前
Linux内核与驱动:9.Linux 驱动 API 封装
linux·c++
Morwit4 小时前
【力扣hot100】 1. 两数之和
数据结构·c++·算法·leetcode·职场和发展
SpiderPex4 小时前
第十七届蓝桥杯 C++ B组-题目 (最新出炉 )
c++·职场和发展·蓝桥杯