news 2026/6/8 17:00:08

【数据结构入门①】从数据结构概念到复杂度分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【数据结构入门①】从数据结构概念到复杂度分析

一,基本概念和术语

1,数据:数据是对客观事物的符号表示,是信息的载体,可以是数字,字符,字符串,图像,声音等。如:182,“李四”,94,“男”。

2,数据元素是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。一个数据元素可以由多个数据项构成,数据项是数据不可分割的最小单位。

如一个结构体{"张三","男","2314050654"}是一个学生的完整信息,也是一个数据元素,其中的学号,姓名等就是一个数据项。

3,数据对象:具有相同性质的数据元素的集合。如老师手里的点名表,有全班同学的姓名,性别,学号。那点名表就是一个数据对象。包含了学生A到学生Z的数据元素。

一句话总结
多个数据项拼出1个数据元素(单个学生),无数同类型数据元素汇总成1个数据对象(全班学生)。


二,数据结构三要素

1,什么是数据结构:数据之间不是独立存在的,它们之间存在某种关系,数据元素之间的关系称为结构。

2,数据结构包含三个方面的内容(三要素):逻辑结构,物理结构(存储结构),数据运算和实现。

3,逻辑结构:描述数据元素之间的逻辑关系。逻辑结构可分为:线性结构、树形结构、图形结构(网状结构)、集合。也可以直接分为线性结构和非线性结构。

①线性结构:线性结构中的数据元素之间存在一对一的关系,除了第一个元素,所有元素都有唯一前驱,除了最后一个元素,所有元素都有唯一后继。非线性结构可能有多个直接前驱和直接后继。

如排队时人们的关系。

②树形结构:树形结构中的数据元素之间存在一对多的关系。

如电脑文件的保存关系。

③图形结构:图形结构中的数据元素之间存在多对多的关系。

如日常生活中的社交关系。

④集合:集合中的数据元素属于一个集合,除此之外别无其他关系。

4,物理结构(存储结构):数据元素及其关系在计算机的存储结构中表示,物理结构主要有四种:顺序存储、链式存储、索引存储、散列存储。

①顺序存储:把逻辑上相邻的数据元素存放在一段连续物理存储单元中,数据元素之间的逻辑关系可以由物理存储关系体现。如存放在数组中。

②链式存储:逻辑上相邻的数据元素存储在任意的一组物理存储单元中,数据元素之间的逻辑关系用指针来表示。

③索引存储:存储元素信息的同时,还建立了索引表,索引表中的每项称为索引项,索引项的一般形式为(关键字,地址)。就像是一个目录,用来查找对应数据。

④散列存储:根据数据元素中的关键字计算出该数据元素的存储地址。

5,数据运算和实现:对数据元素可以施加的操作,以及这些操作在相应的存储结构上的实现。

三,数据类型和抽象数据类型

数据类型是一个值的集合和定义在这个值集上的一组操作的总称,数据类型一般可分为:原子类型和结构类型。
a. 原子类型:原子类型的值是不可以再分解的。如C语言的int、double、char等基本数据类型
b. 结构类型:结构类型的值是由若干成分按某种结构组成,是可以分解的。如C语言中定义的一个结构体类型。

抽象数据类型:Abstract Data Type, ADT:是一种数学模型加上在该模型上定义的一组操作的集合,它定义了数据对象、数据关系以及对数据的操作,但隐藏了具体实现细节。

四,算法的基本概念

1,算法是解决特定问题的求解步骤的一种描述。

2,算法 + 数据结构 = 程序。

3,算法的特性:

①有穷性:算法必须在有限步骤内结束,禁止无限循环,永不终止。

②确定性:算法每一条指令定义精准、无歧义,相同输入经过运算,必定得到完全一致的输出结果。

③可行性:算法中所有运算步骤,都能依托已实现的基础运算有限次落地执行。

④输入:输入数量≥0,输入数据来源于指定的数据对象集合。

⑤输出:输出数量≥1,输出结果由输入数据经过算法处理生成,和输入具备对应逻辑关系。

4,算法的设计要求

①正确性

算法能够精准完成目标问题求解,针对合法输入产出正确答案。

②可读性

代码逻辑思路通俗易懂、注释规范,便于后续阅读、调试与二次维护。

③健壮性

面对非法、异常输入时,算法可合理处理异常,不会出现崩溃、乱输出等莫名错误。

④高效率与低存储(时空性能)

- 时间效率:尽可能缩短代码运行耗时;

- 空间效率:程序运行期间占用的内存资源越少越好;
二者合称算法的时间复杂度、空间复杂度,是衡量算法优劣的核心指标。

五,算法的时间复杂度

一、为什么要分析算法效率

同一个问题可以有多种不同的算法实现,评价一个算法的优劣有明确的优先级:
正确性 > 可读性 > 健壮性 > 算法效率

在满足前三个基本要求后,我们主要通过算法效率来选择最优方案。算法效率分为两类:

- 时间效率:算法运行时所耗费的时间
- 空间效率:算法运行过程中额外耗费的存储空间

现在重点讲解最常用的时间效率分析方法——时间复杂度。

二、时间效率的两种分析方法

1. 事后统计法

先完整实现算法,然后运行并统计实际耗时。
缺点:严重依赖运行环境(编程语言、CPU、编译器、操作系统),不同机器上的结果差异极大,好的设备就快,不好的设备就慢,容易掩盖算法本身的优劣;且必须先写代码才能测试,成本高。

2. 事前估算法(核心方法)

在代码编写前,通过数学方法估算算法的执行效率,完全脱离硬件和软件环境,是行业通用的标准方法。

事前估算的核心公式

算法的总运行时间 = 所有代码语句的执行时间之和

运行时间 =

-:第 i 条语句单次执行消耗的时间(与硬件,编译器相关)。

-:第 j 条代码语句的执行次数(仅与算法逻辑相关)。

公式简化

由于与算法本身无关,为了统一标准,我们做一个合理假设:所有代码语句单次执行的时间相同,即=1。
此时,算法的运行时间就简化为所有代码语句的执行次数之和,记为函数f(N)(N为输入数据的规模)。

示例:累加算法的事前估算

int Summation(int N) { int ret = 0; // 执行1次 int i = 1; // 执行1次 while (i <= N) { // 执行N+1次(最后一次判断不进入循环) ret += i; // 执行N次 i++; // 执行N次 } return ret; // 执行1次 }

总执行次数:f(N) = 1+1+(N+1)+N+N+1 = 3N+4次。

三、时间复杂度渐进表示法(大O表示法)

1. 为什么需要渐进表示

我们真正关心的不是小数据量下的绝对执行次数,而是当输入规模N无限增大时,算法执行次数的增长速度(增长趋势)。

例如:

- 算法A:(N)=100*N
- 算法B:(N)=

当N=50时,A执行5000次,B执行2500次,B更快;但当N=1000时,A执行100000次,B执行1000000次,A快得多。随着N继续增大,A的优势会越来越明显。

因此,算法分析的核心是比较执行次数随规模变化的增长速度,而不是具体的数值。

2. 大O表示法的数学定义

若存在常数C和,使得当时,有,则称T(N)的渐进时间复杂度为O(f(N)),简称时间复杂度。

(C为非零有限常数)


大O表示法描述的是算法运行时间的上界,即最坏情况下的增长趋势。


3. 大O表示法的计算规则

对执行次数函数f(N)做如下简化,即可得到时间复杂度:

①只保留最高阶项:当N无限大时,最高阶项对增长速度起决定性作用
②去除最高阶项的系数:系数不影响增长量级
③去除所有常数项:常数项与输入规模N无关
④若没有与N相关的项(只有常数):时间复杂度记为O(1)

4,快速计算技巧(实战必备)

实际开发中不需要逐行统计执行次数,记住以下技巧即可快速判断:

1. 只关注循环语句:普通语句的执行次数都是常数,可以直接忽略
2. 只看循环内部的基本操作:循环的条件判断、自增等语句的影响最终都会被系数抵消
3. 嵌套循环看层数:一层循环通常是O(N),两层嵌套是O(),三层是O()注意:不是所有循环都是O(N),例如二分查找的循环是O(logN),需要结合具体逻辑计算

5. 对数的统一表示

根据对数换底公式:


其中是一个常数,因此所有底数的对数在渐进表示中都是同一量级。
行业通用写法:统一用O(logN)表示,部分书籍也会写成O(lgN)。

四、常见时间复杂度量级排序

从最优到最差的顺序:

O(1) < O(logN) < O(N) < O(N*logN) < O(N^2) < O(N^3) < O(2^N) < O(N!)


- O(1):常数级,最优,如数组随机访问
- O(logN):对数级,非常高效,如二分查找
- O(N):线性级,如遍历数组
- O(N*logN):线性对数级,如快速排序、归并排序
- O():平方级,如冒泡排序、选择排序
- O()、O(N!):指数级和阶乘级,效率极低,仅适用于极小数据量

五、经典样例分析

O(N) 线性级

对应上面的累加算法,总执行次数f(N)=3N+4,简化后时间复杂度为T(N)=O(N)。

六,常见样例分析

样例1:O(N) 线性级

核心特征:单层循环,循环次数与输入规模 N 成正比。

// 样例1:累加求和算法 int Summation(int N) { int ret = 0; int i = 1; while (i <= N) { ret += i; i++; } return ret; }


计算过程:循环体 ret += i 和 i++ 各执行N次,忽略常数项和系数。
结论:T(N)=O(N)
典型场景:数组遍历、链表遍历、顺序查找。

样例2:O(N²) 平方级

核心特征:两层嵌套循环,每层循环次数都与 N 成正比。

样例2:冒泡排序算法 #include <assert.h> void Swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } void BubbleSort(int* a, int n) { assert(a); // 外层循环控制排序轮数 for (size_t end = n; end > 0; --end) { // 内层循环进行相邻元素比较交换 for (size_t i = 1; i < end; ++i) { if (a[i-1] > a[i]) { Swap(&a[i-1], &a[i]); } } } }

计算过程:
总比较交换次数 = 1+2+3+...+(n-1) = \frac{n(n-1)}{2}
保留最高阶项,忽略系数和低阶项。
结论:T(N)=O()
典型场景:冒泡排序、选择排序、插入排序。


样例3:O(N³) 立方级

核心特征:三层嵌套循环,每层循环次数都与 N 成正比。

样例3:N阶方阵乘法 #define N 3 void MatrixMultiply(int A[][N], int B[][N], int C[][N]) { // 核心计算部分:三层嵌套循环(决定最高阶) for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { C[i][j] = 0; for (int k = 0; k < N; k++) { C[i][j] += A[i][k] * B[k][j]; } } } // 打印结果(O(N²),低阶项不影响最终复杂度) for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { printf("%d ", C[i][j]); } printf("\n"); } }

计算过程:核心乘法部分执行次,打印部分执行次,只保留最高阶项。
结论:T(N)=O()
典型场景:矩阵乘法、Floyd最短路径算法。

样例4:O(logN) 对数级(极高效)

核心特征:循环变量每次按倍数增长/衰减(×2、÷2等)。

样例4:倍增循环计数 #include <stdio.h> void Count(int n) { // i从1开始,每次乘以2,直到大于n for (int i = 1; i < n; i *= 2) { printf("%d\n", i); } }

计算过程:
设循环执行了x次,则终止条件为
两边取对数得:
所有底数的对数在大O表示中是同一量级,统一记为O(logN)。
结论:T(N)=O(logN)
典型场景:二分查找、二叉树高度计算、快速幂算法。

样例5:O(1) 常数级(最优)

核心特征:执行次数是固定常数,与输入规模 n 完全无关。

样例5:固定次数打印 void Print100(int* a, int n) { // 循环最多执行100次,无论n多大都不变 for (int i = 0; i < n && i < 100; i++) { printf("%d ", a[i]); } printf("\n"); }

计算过程:循环执行次数≤100,是一个固定常数。
结论:T(N)=O(1)
典型场景:数组随机访问、哈希表查找、简单算术运算。

样例6:O(M+N) 多变量线性级

核心特征:多个独立的循环,分别对应不同的输入规模。

样例6:两个独立循环打印 void PrintMN(int m, int n) { // 第一个循环执行m次 for (int i = 0; i < m; ++i) { printf("hello\n"); } // 第二个循环执行n次 for (int i = 0; i < n; ++i) { printf("hello\n"); } }

计算过程:总执行次数 = m + n,取最高阶项。
结论:T(N)=O(M+N) 或 O(max(M,N))
实战技巧:多个独立循环的复杂度相加,只保留最大的那个量级。

样例7:最好、最坏、平均时间复杂度

有些算法的执行次数会随输入数据的分布而变化,因此需要分三种情况讨论:

- 最坏时间复杂度:最坏情况下的执行次数(行业默认使用,保守估计)
- 最好时间复杂度:最好情况下的执行次数(算法下限)
- 平均时间复杂度:所有输入情况的期望执行次数

int Find(int* a, int n, int x) { for (int i = 0; i < n; i++) { if (a[i] == x) { return i; // 找到就立即返回 } } return -1; // 没找到 }

详细分析:

1. 最好情况:要找的元素在数组第一个位置,执行1次 → T(N)=O(1)
2. 最坏情况:要找的元素在数组最后一个位置或不存在,执行n次 → T(N)=O(N)
3. 平均情况:假设每个位置出现的概率相等,平均执行次数为:

→ T(N)=O(N)

重要结论:我们平时说的算法时间复杂度,默认指的是最坏时间复杂度。

样例8:递归算法的时间复杂度

核心公式:递归算法的时间复杂度 = 递归调用的次数 × 每次递归内部的操作复杂度。

样例8:递归求阶乘 long long Fac(size_t N) { if (0 == N) { return 1; // 递归终止条件,O(1) } return Fac(N-1) * N; // 递归调用,O(1) }

计算过程:

- 递归调用次数:从N到0,共调用N+1次
- 每次递归内部的操作:乘法和条件判断,都是O(1)
- 总复杂度:(N+1)O(1) = O(N)

结论:T(N)=O(N)

七、核心总结

1. 时间复杂度描述的是算法运行时间随输入规模增长的趋势,不是绝对运行时间
2. 大O表示法关注的是最坏情况下的增长量级,忽略常数、系数和低阶项
3. 计算时间复杂度时,只需要看循环的嵌套层数和逻辑,不需要逐行统计
4. 时间复杂度是衡量算法优劣的核心指标,也是面试中算法题的必考内容

六,空间复杂度分析

一、核心定义

空间复杂度描述的是算法为了实现功能,额外开辟的存储空间随输入规模N增长的变化趋势。
⚠️ 最关键的区分:算法输入数据本身占用的空间不计入空间复杂度,我们只统计算法为了完成计算,主动申请的临时变量、数组、栈、队列等额外空间。

二、计算方法(与时间复杂度完全一致)

和时间复杂度的事前估算法逻辑完全相同,步骤如下:

1. 假设每个基础变量占用的空间大小为1(忽略不同数据类型的字节差异)
2. 统计算法运行过程中,额外申请的所有变量/空间的总数量,得到函数f(N)
3. 保留最高阶项,忽略所有常数项和系数,最终用大O表示法记为 S(N)=O(f(N))

三、常见空间复杂度量级(从优到劣)

和时间复杂度的量级排序完全一致,日常开发中最常用的是前四个:
O(1)好于O(logN)好于O(N)好于O()

- O()、O(N!) 等指数/阶乘级空间复杂度在实际工程中几乎不会使用。

四、核心概念:原地算法

如果算法在执行过程中,只需要常数级别的额外空间(即空间复杂度为O(1)),就称为原地算法。
原地算法是空间效率最优的算法,不需要额外开辟大量内存,在嵌入式、大数据等内存受限的场景中尤为重要。

五、时间复杂度 vs 空间复杂度 核心对比

维度 时间复杂度 空间复杂度
核心关注点 算法执行次数的增长趋势 算法额外空间的增长趋势
计算依据 循环、递归的执行次数 额外申请的变量、数组数量
最优情况 (常数次执行) (原地算法)
计算规则 保留最高阶,忽略常数和系数 与时间复杂度完全相同

六、核心总结

1. 时间看“跑多久”,空间看“占多少内存”,二者计算规则完全一致
2. 空间复杂度只算算法自己额外申请的空间,输入数据的空间不算
3. 算法设计中常做时空权衡:用少量额外空间换取更快的运行速度,或用更长的运行时间节省内存
4. 优先追求O(1)的原地算法,在内存受限场景下是刚需

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 16:57:03

基于i.MX RT1060与DMA实现高速RS-485通信的工程实践

1. 项目概述在工业自动化、智能电表或者楼宇控制这类项目中&#xff0c;RS-485总线几乎是绕不开的通信标准。它那套差分平衡传输的机制&#xff0c;天生就适合在几十米甚至上千米的距离上&#xff0c;顶着各种电机、变频器带来的电磁干扰&#xff0c;稳定地传递数据。但真要把R…

作者头像 李华
网站建设 2026/6/8 16:56:19

WRF namelist.input 和 namelist.wps 参数设置详解:以一次FNL数据模拟为例

WRF namelist.input 和 namelist.wps 参数设置详解&#xff1a;以一次FNL数据模拟为例当WRF模型的基础安装和简单案例运行已经不再是障碍&#xff0c;真正困扰研究者的往往是那些隐藏在配置文件中的关键参数。这些看似简单的数字和选项背后&#xff0c;实则决定了模拟的精度、稳…

作者头像 李华
网站建设 2026/6/8 16:52:44

Stable Baselines3 实战指南:用5行代码构建生产级强化学习系统

Stable Baselines3 实战指南&#xff1a;用5行代码构建生产级强化学习系统 【免费下载链接】stable-baselines3 PyTorch version of Stable Baselines, reliable implementations of reinforcement learning algorithms. 项目地址: https://gitcode.com/GitHub_Trending/st/…

作者头像 李华
网站建设 2026/6/8 16:51:54

tchMaterial-parser:一键获取国家中小学智慧教育平台电子课本的终极指南

tchMaterial-parser&#xff1a;一键获取国家中小学智慧教育平台电子课本的终极指南 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具&#xff0c;帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载&#xff0c;让您更方便地获取课本…

作者头像 李华
网站建设 2026/6/8 16:50:44

VCF 4.0 SDDC Manager资源要求详解!8vCPU+32GB内存标准配置教程

部署VMware VCF 4.0私有云平台时&#xff0c;SDDC Manager是整个云基座的核心管控中枢&#xff0c;资源配置不足极易出现部署卡顿、任务超时、服务宕机等顽固问题。VCF 4.0环境中&#xff0c;SDDC Manager官方生产推荐配置为8vCPU、32GB内存及以上&#xff0c;低配仅适用于极简…

作者头像 李华