数据结构从入门到精通:算法复杂度

1. 数据结构的介绍

一、核心基础概念

1.1 数据结构

定义计算机存储、组织数据的方式 ,是相互间存在特定关系的数据元素集合。常见类型:线性表、树、图、哈希表等。

1.2 算法

定义 :定义良好的计算过程,接收输入值,经过计算步骤输出结果,是输入到输出的转化逻辑。

1.3 数据结构 & 算法的求职重要性

校招笔试、面试必考核心内容,列举了多家大厂真实考察场景:

  • 笔试:美团、科大讯飞等笔试包含编程题,直接考察算法实现;
  • 面试:深信服、腾讯、猎豹科技等面试高频提问:数组与链表区别、时间复杂度、堆、归并排序、手写算法(全排列、最长公共子数组等)、底层原理实现等。

二、学习方法(2 个核心秘诀)

  1. 死磕代码:必须手写代码实现,不能只看理论;
  2. 画图 + 思考:用画图具象化数据结构逻辑,深度理解原理。

三、经典书籍推荐

书籍名称 版本 / 特点 推荐理由
《数据结构》严蔚敏 C 语言版 内容详尽、代码规范,高校指定经典教材
《数据结构》殷人昆 C++ 版 面向对象讲解,适配 C++ 开发,代码规整
《算法导论》托马斯・科尔曼 经典原版 叙述严谨,系统全面,深入讲解各类算法原理
《大话数据结构》程杰 通俗趣味版 语言易懂,算法讲解生动,适合入门

2. 算法效率

一、案例引入:旋转数组(LeetCode)

1. 基础代码(逐位后移)

复制代码
void rotate(int* nums, int numsSize, int k) {
    while(k--)
    {
        int end = nums[numsSize-1];
        for(int i = numsSize - 1;i > 0 ;i--)
        {
            nums[i] = nums[i-1];
        }
        nums[0] = end;
    }
}

2. 问题核心

代码局部运行可通过,但无法通过平台提交 ,本质是算法效率过低,因此需要用复杂度衡量算法好坏。


二、复杂度的概念

算法运行 会消耗时间资源空间资源,因此从两个维度评判:

  1. 时间复杂度:衡量算法运行快慢
  2. 空间复杂度:衡量算法运行所需额外内存

💡 行业现状:早期硬件存储有限,空间复杂度很受重视;如今硬件性能提升,时间复杂度成为核心评判标准


三、复杂度的重要性

复杂度是大厂校招笔试、面试必考知识点,图片给出真实面试案例:

  1. 腾讯 C++ 后台实习一面 :直接考察「快排、堆排序时间复杂度及最坏情况推导
  2. 剑指 Offer 真题 :要求算法满足时间复杂度 O (n)、空间复杂度 O (1)
  3. 校招规律:笔试算法题、技术面试均会考察复杂度计算与原理理解

3. 时间复杂度基础定义

一、核心概念

  1. 核心概念 时间复杂度用函数式 T(N) 定量描述算法运行效率不直接统计真实运行时间,原因:
  • 运行时间受编译器、机器配置影响,不通用
  • 需提前理论评估,无法等代码写完测试
  • 代码执行次数代替真实时间,和运行时间成正比
  • 程序执行的时间 = 二进制指令运行的时间 (cpu处理二进制指令非常快可以近似认为是一样的) * 执行次数

本质 :计算程序执行次数的函数,只对比增长量级,而非精确次数。


二、大 O 渐进表示法(核心推导规则)

用于简化时间复杂度,只关注数据量大时的最坏增长趋势3 条规则

  1. 只保留最高阶项去掉低阶项N 极大时低阶影响可忽略
  2. 去掉最高阶项常数系数
  3. 无 N 相关项、只有常数时统一记为 O(1)

三、时间复杂度计算示例(高频考点)

示例 1 Func1

复制代码
#include <stdio.h>

// Func1 完整代码
void Func1(int N)
{
    int count = 0;

    // 第一层:双层嵌套循环 -> 执行 N*N 次
    for (int i = 0; i < N; ++i)
    {
        for (int j = 0; j < N; ++j)
        {
            ++count;
        }
    }

    // 第二层:单层循环 -> 执行 2*N 次
    for (int k = 0; k < 2 * N; ++k)
    {
        ++count;
    }

    // 第三层:固定循环 -> 执行 10 次
    int M = 10;
    while (M--)
    {
        ++count;
    }

    // 输出最终执行次数
    printf("N = %d 时,总执行次数 count = %d\n", N, count);
}

// 主函数:测试运行
int main()
{
    // 测试 N = 10
    Func1(10);
    return 0;
}

1. 双层嵌套循环

复制代码
for (int i = 0; i < N; ++i)
    for (int j = 0; j < N; ++j)
        ++count;
  • 外层循环跑 N 次
  • 内层循环跑 N 次
  • 总次数:N × N = N²

2. 单层循环

复制代码
for (int k = 0; k < 2 * N; ++k)
    ++count;
  • 循环跑 2N 次
  • 总次数:2N

3. 固定次数循环

复制代码
int M = 10;
while (M--)
    ++count;
  • 固定跑 10 次,和 N 无关
  • 总次数:10

总执行次数公式

复制代码
T(N) = N² + 2N + 10

时间复杂度为: O(N^2)

示例 2 Func2

复制代码
void Func2(int N)
{
    int count = 0;
    for (int k = 0; k < 2 * N ; ++k)
    {
        ++count;
    }
    int M = 10;
    while (M--)
    {
        ++count;
    }
}
  • 循环 1:执行**2N 次**
  • 循环 2:固定执行**10 次**
  • 总次数:T(N)=2N+10
  • 时间复杂度:O (N)去掉常数、系数,只保留最高阶

示例 2 Func3

复制代码
void Func3(int N, int M)
{
    int count = 0;
    for (int k = 0; k < M; ++k) ++count;
    for (int k = 0; k < N ; ++k) ++count;
}
  • 总次数:T(N)=M+N
  • 时间复杂度:O (N+M)两个未知量都保留

示例 3 Func4

复制代码
void Func4(int N)
{
    int count = 0;
    for (int k = 0; k < 100; ++k) ++count;
}
  • 循环固定执行 100 次,和 N 无关
  • 时间复杂度:O (1)常数复杂度

示例 4 strchr(查找字符)

cpp 复制代码
#计算strchr的时间复杂度?
const char *strchr(const char* str,char character)
{
  const char* p_begin =s;
  while(*p_begin !=character)
  {

    if(*p_begin='\0')
         return NULL;
         p_begin++ ;    
   }
     return p_begin;
}
  • 最好情况:字符在第 1 位O(1)
  • 最坏情况:字符在最后 / 不存在O(N)
  • 平均情况:O(N)

大 O 关注最坏情况 ,所以一般写 O (N)


示例 5 冒泡排序 BubbleSort

复制代码
void BubbleSort(int* a, int n)
{
    for (size_t end = n; end > 0; --end)
    {
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
            if (a[i-1] > a[i]) Swap(&a[i-1],&a[i]);
        }
    }
}
  • 有序最好情况:O**(N)**
  • 逆序最坏情况:T(N)=n(n-1)/2
  • 时间复杂度:O (N²)

示例 6 func5(对数复杂度)

复制代码
void func5(int n)
{
    int cnt = 1;
    while (cnt < n)
    {
        cnt *= 2;
    }
}
  • 循环条件:2^x = nx = log₂n
  • 时间复杂度:O (logN) (底数省略)(当n趋于无穷大时 底数的大小对结果影响不大

示例 7 阶乘递归 Fac

复制代码
long long Fac(size_t N)
{
    if(0 == N) return 1;
    return Fac(N-1)*N;
}
  • 递归调用 N 次
  • 时间复杂度:O (N)

计算递归算法的时间复杂度 =单词递归的时间复杂度 * 递归次数


核心总结(必背)

  1. 只看最高阶项 ,去掉常数、系数
  2. 单层循环 →O (N) ;嵌套循环 →O (N²)
  3. 每次翻倍 / 折半O (logN)
  4. 固定次数 →O (1)
  5. 大 O 默认看最坏情况

4. 空间复杂度

一、核心概念📌

空间复杂度 用于衡量算法运行时额外临时开辟的存储空间 规模,采用大 O 渐进表示法和时间复杂度计算规则类似。

注意:
空间复杂度不是程序占⽤了多少bytes的空间,因为常规情况每个对象⼤⼩差异不会很⼤,所以空间复杂度算的是变量的个数

  • 不计入:函数栈帧中编译期就确定的空间(参数、局部变量、寄存器信息)。
  • 只计算:运行时显式申请 的额外空间(动态数组、递归调用栈等)。

二、计算示例解析

示例 1:冒泡排序 BubbleSort

复制代码
void BubbleSort(int* a, int n)
{
    assert(a);
    for (size_t end = n; end > 0; --end)
    {
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
            if (a[i-1] > a[i])
            {
                Swap(&a[i-1], &a[i]);
                exchange = 1;
            }
        }
        if (exchange == 0)
            break;
    }
}
  • 额外空间:仅**exchange,end,i** 等常数个局部变量 ,空间不随n变化。(数组是传入的数据不算入
  • 空间复杂度:O(1)(常数空间)

示例 2:递归阶乘 Fac

复制代码
long long Fac(size_t N)
{
    if(N == 0)
        return 1;
    return Fac(N-1)*N;
}
  • 额外空间:递归调用N ,产生N 个函数调用栈帧,栈帧数量随N线性增长。
  • 空间复杂度:O(N)(线性空间)

5.常见复杂度的对比

时间复杂度从慢到快:

O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ) < O(n!)

复杂度越大 程序运行越慢 运行效率越低 程序设计差

6. 旋转数组 3 种思路完整解析(回归算法效率的探究)

题目要求

给定数组,将数组元素向右旋转 k 步 (例:[1,2,3,4,5,6,7],k=3 → [5,6,7,1,2,3,4]


思路 1:暴力右移(每次移 1 位,循环 k 次)

复杂度太高 效率低下

  • 时间:O(N^2 ),最坏 k=n 时,双层循环

  • 空间:O(1)只有temp i 两个变量

  • 缺点:数据量大时超时,LeetCode 无法通过

    void rotate(int* nums, int numsSize, int k) {
    while(k--)
    {
    int end = nums[numsSize-1];
    for(int i = numsSize - 1;i > 0 ;i--)
    {
    nums[i] = nums[i-1];
    }
    nums[0] = end;
    }
    }


思路 2:额外数组(空间换时间)(将后k个数据放到前k个位置)

复杂度适当大 效率相当

  • 时间:O(N)遍历 2 次数组

  • 空间:O(N)需要新数组存储数据

  • 优点:逻辑简单,不会超时;缺点占用额外内存

    void rotate(int* nums, int numsSize, int k)
    {
    int newArr[numsSize];
    for (int i = 0; i < numsSize; ++i)
    {
    newArr[(i + k) % numsSize] = nums[i];
    }
    for (int i = 0; i < numsSize; ++i)
    {
    nums[i] = newArr[i];
    }
    }


思路 3:三次反转(最优解法,原地操作)

复杂度低 效率高

核心逻辑(示例:1234567,k=3)

  1. 反转前**n−k** 个:4321567
  2. 反转后**k** 个:4321765
  3. 整体反转:5671234(最终结果)
  • 先取模:k = k%numsSize,避免 k 大于数组长度

复杂度

  • 时间:O(N)

  • 空间:O(1),原地修改,最优解

    void reverse(int* nums,int begin,int end)
    {
    while(begin<end){
    int tmp = nums[begin];
    nums[begin] = nums[end];
    nums[end] = tmp;
    begin++;
    end--;
    }
    }
    void rotate(int* nums, int numsSize, int k)
    {
    k = k%numsSize;
    reverse(nums,0,numsSize-k-1);
    reverse(nums,numsSize-k,numsSize-1);
    reverse(nums,0,numsSize-1);
    }


最优方案对比

思路 时间复杂度 空间复杂度 推荐度
暴力右移 O(N^2) O(1)
额外数组 O(N) O(N) ⭐⭐⭐
三次反转 O(N) O(1) ⭐⭐⭐⭐⭐
相关推荐
夜雪闻竹1 小时前
从零实现 Embedding 服务:文本转向量
人工智能·算法·embedding
weixin_429630261 小时前
3.41 一种高效的室内定位Wi-Fi视觉地图构建及自维护方法
算法
心中有国也有家1 小时前
NPU性能调优完全攻略——从Profiler到算子调优的实战方法论
人工智能·经验分享·笔记·分布式·算法
Gauss松鼠会1 小时前
GaussDB(DWS)数据融合:Oracle增量数据迁移到DWS
java·数据库·算法·oracle·性能优化·gaussdb
洛水水2 小时前
【力扣100题】51.不同路径
算法·leetcode·职场和发展
Emerson_20262 小时前
set/multiset和map/multimap
数据结构·c++·set/multiset·map/mulmulti
ん贤2 小时前
KServe 详细笔记
笔记·算法·贪心算法
吴可可1232 小时前
C#处理CAD文件的主流库推荐
算法
MicroTech20252 小时前
微算法科技(NASDAQ :MLGO)基于量子傅立叶变换的量子彩色图像加密算法
科技·算法·量子计算