第一章 绪论
第一章 绪论
1.1 数据结构的基本概念
一、核心定义
数据(Data):信息的载体,是对客观事物符号化的表示,能够被计算机识别、存储和加工处理。
🗣️ 大白话:数据就是计算机能认识的东西。你的名字、年龄、一张照片、一段视频,只要能存进电脑里的,都叫数据。
数据元素(Data Element):数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。一个数据元素可由若干个数据项(Data Item) 组成,数据项是构成数据元素的不可分割的最小单位。
🗣️ 大白话:好比一个学生信息表,每一行(一个学生的全部信息)就是一个数据元素,而"姓名""学号""成绩"这些列就是数据项。数据项是最小的,不能再拆了。
数据对象(Data Object):性质相同的数据元素的集合,是数据的一个子集。
数据结构(Data Structure):相互之间存在一种或多种特定关系的数据元素的集合。包含三个方面:
| 方面 | 含义 | 说明 |
|---|---|---|
| 逻辑结构 | 数据元素之间的逻辑关系 | 与存储无关,是从逻辑上描述数据 |
| 存储结构(物理结构) | 数据结构在计算机中的表示 | 逻辑结构在计算机内存中的映像 |
| 数据的运算 | 施加在数据上的操作 | 运算的定义针对逻辑结构,实现针对存储结构 |
二、逻辑结构的分类
逻辑结构
├── 线性结构:元素之间是一对一的关系(如线性表、栈、队列)
└── 非线性结构
├── 集合结构:元素之间除"属于同一集合"外无其他关系
├── 树形结构:元素之间是一对多的关系(如二叉树、B树)
└── 图状结构(网状结构):元素之间是多对多的关系(如有向图、无向图)
🗣️ 大白话:
- 线性结构就像排队——每个人前面最多一个人,后面也最多一个人,一条线串起来。
- 树形结构就像家族族谱——一个爷爷可以有好几个儿子,但每个人只有一个亲爹。
- 图状结构就像微信朋友圈——你认识我,我认识他,他也可能认识你,关系错综复杂。
- 集合结构就像一筐苹果——它们之间没啥特别关系,只是放在了一起。
三、存储结构的四种基本方式
| 存储方式 | 特点 | 典型应用 |
|---|---|---|
| 顺序存储 | 逻辑上相邻的元素物理上也相邻,用一组连续的存储单元 | 数组、顺序表 |
| 链式存储 | 不要求物理相邻,通过指针表示逻辑关系 | 链表 |
| 索引存储 | 在存储元素信息的同时,建立附加的索引表 | 索引文件 |
| 散列存储 | 根据元素的关键字直接计算出存储地址 | 散列表(Hash表) |
🗣️ 大白话:
- 顺序存储:就像图书馆的书架,书按编号挨个排好,找第5本直接数到第5个位置。
- 链式存储:就像寻宝游戏,每本书里夹了一张纸条,告诉你下一本书在哪。
- 索引存储:就像书的目录,先查目录找到页码,再翻到那一页。
- 散列存储:就像按名字首字母分类放东西,姓"张"的放到Z区,直接去那个区找。
四、数据类型与抽象数据类型
数据类型(Data Type):一个值的集合和定义在此集合上的一组操作的总称。
抽象数据类型(ADT, Abstract Data Type):一个数学模型以及定义在该模型上的一组操作。ADT的定义仅取决于它的一组逻辑特性,与其在计算机内部的表示和实现无关。
🗣️ 大白话:ADT就是"你只管用,不用管我怎么做到的"。就像你用手机打电话,你只知道拨号就能打通,至于信号怎么传输的你不需要管——这就是"抽象"。
1.2 算法与算法评价
一、算法的基本概念
算法(Algorithm):对特定问题求解步骤的一种描述,是指令的有限序列。
算法的五个重要特性:
| 特性 | 含义 | 说明 |
|---|---|---|
| 有穷性 | 算法必须在执行有穷步之后结束,且每一步都在有穷时间内完成 | 程序可以不满足(如操作系统) |
| 确定性 | 每条指令必须有确切含义,不产生二义性 | 相同输入只能得到相同输出 |
| 可行性 | 算法中的操作都可以通过已实现的基本运算执行有限次来实现 | — |
| 输入 | 零个或多个输入 | 可以没有输入 |
| 输出 | 一个或多个输出 | 必须有输出 |
⚠️ 考研高频考点:算法与程序的区别——算法必须是有穷的,而程序可以是无穷的(如操作系统是一直运行的程序)。
"好"算法的评价标准:
- 正确性:能正确解决问题
- 可读性:易于阅读理解
- 健壮性:能对非法输入做出适当反应
- 效率与低存储量:时间复杂度低、空间复杂度低
二、时间复杂度
定义:算法中基本操作的执行次数 是问题规模 的函数,取 的数量级(最高阶项),记作:
表示当 趋于无穷大时, 的增长率与 的增长率相同。
常见时间复杂度排序(从低到高):
📝 记忆口诀:"常对根线,线对平立,指阶阶阶"——常数、对数、根号、线性、线性对数、平方、立方、指数、阶乘。简化记为"常对幂指阶"(常数 < 对数 < 幂函数 < 指数函数 < 阶乘)。
⚠️ 注意 介于 和 之间,考研真题偶尔出现。
🗣️ 大白话:时间复杂度就是衡量"当数据量变大时,算法变慢的速度"。 意味着不管数据多少,速度都一样快(比如查数组的第k个元素); 意味着数据翻倍,时间也翻倍(比如遍历数组); 意味着数据翻倍,时间变4倍(比如冒泡排序)。
三种复杂度:
- 最坏时间复杂度:最不利情况下的时间复杂度(考研默认分析这个)
- 平均时间复杂度:所有可能输入的平均时间
- 最好时间复杂度:最理想情况下的时间复杂度(一般不讨论)
常见代码的时间复杂度分析方法:
// O(1) —— 常数阶
int x = 1;
int y = x + 1;
// O(n) —— 线性阶
for (int i = 0; i < n; i++)
x++;
// O(n²) —— 平方阶
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
x++;
// O(log₂n) —— 对数阶
while (n > 1)
n = n / 2;
// O(n·log₂n) —— 线性对数阶
for (int i = 0; i < n; i++)
for (int j = 1; j < n; j = j * 2)
x++;
// O(1) —— 常数阶
int x = 1;
int y = x + 1;
// O(n) —— 线性阶
for (int i = 0; i < n; i++)
x++;
// O(n²) —— 平方阶
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
x++;
// O(log₂n) —— 对数阶
while (n > 1)
n = n / 2;
// O(n·log₂n) —— 线性对数阶
for (int i = 0; i < n; i++)
for (int j = 1; j < n; j = j * 2)
x++;
三、空间复杂度
定义:算法所需存储空间 关于问题规模 的数量级,记作:
⚠️ 注意:空间复杂度只计算算法额外使用的辅助空间,不包括输入数据本身占用的空间。
算法原地工作:指算法所需的辅助空间为常量,即 。
🗣️ 大白话:空间复杂度就是你算法运行时"额外"需要多少空间。就像你收拾房间,你已有的东西不算,你额外需要买几个收纳箱——那就是空间复杂度。如果不需要额外买箱子就能搞定(原地工作),那就是 。
📝 真题剖析
【2011年408真题】 设 是描述问题规模的非负整数,下面程序段的时间复杂度是()。
x = 2;
while (x < n/2)
x = 2 * x;
int x = 2;
while (x < n / 2)
x = 2 * x;
A. B. C. D.
解题方法:
设循环执行 次。每次 变为 ,初始 。
- 第1次后:
- 第2次后:
- 第 次后:
循环结束条件:,即
所以 。答案选A。
💡 解题套路:看到变量成倍增长()或成倍缩减(),八成就是 级别。
【2012年408真题】 求 的递归算法(fact(n)),其时间复杂度为()。
int fact(int n) {
if (n <= 1) return 1;
return n * fact(n - 1);
}
A. B. C. D.
解:递推关系 (),。展开得 。答案选B。
💡 递归时间复杂度分析套路:列出递推关系式,展开或用主定理求解。每层递归只做 工作、递归 层 → 。
【递归复杂度分析利器——主定理(Master Theorem)简化版】
对于形如 的递推关系():
| 条件 | 结论 | 记忆 |
|---|---|---|
| (子问题增长慢于合并代价) | 合并代价主导 | |
| 各层均等,乘以层数 | ||
| (子问题增长快于合并代价) | 叶子数量主导 |
常考实例:
| 递推关系 | 比较 | 复杂度 | |||
|---|---|---|---|---|---|
| (归并排序) | 2 | 2 | 1 | ||
| (遍历二叉树) | 2 | 2 | 0 | ||
| (折半查找) | 1 | 2 | 0 | ||
| 2 | 4 | 0 | |||
| — | — | — | 不适用主定理(非 形式),直接展开得 |
⚠️ 注意:主定理仅适用于"规模缩小为 "的递推关系。如 (如选择排序的递归版)属于递减型递推,需直接展开:。
【摊还分析(Amortized Analysis)——高级复杂度分析】
某些操作的单次最坏代价很高,但连续多次操作的平均代价很低。摊还分析给出的是操作序列的平均上界,比最坏情况分析更精确。
| 方法 | 思想 | 典型应用 |
|---|---|---|
| 聚合分析 | 计算 次操作的总代价 ,摊还代价 | 动态数组扩容 |
| 核算法 | 为"便宜操作"多收费存为"信用","贵操作"用信用支付 | 栈的 multipop |
| 势能法 | 定义势函数 ,摊还代价 | 并查集、Splay树 |
🗣️ 大白话:就像你每月存100块钱,到年底一次性花1200修车。每次花钱看起来很"便宜"(月均100),但修车那次本身花了1200。摊还分析就是把1200分摊到12个月来看。
典型例子——动态数组扩容:顺序表满了就翻倍扩容,扩容时复制所有元素代价为 。但从空开始连续插入 个元素,扩容总代价为 。因此每次插入的摊还代价为 ,而非最坏的 。
考试关键:并查集使用按秩合并 + 路径压缩后, 次操作的总代价为 ,其中 是反阿克曼函数(增长极慢,对所有实际情况 )。因此单次操作的摊还代价近似 。
【2017年408真题】 下面程序段的时间复杂度是()。
int sum = 0, i = 1;
while (sum < n)
sum += ++i;
A. B. C. D.
解:设循环执行 次,则 。当 时结束,即 ,解得 。答案选B。
💡 解题套路:当累加和与 比较时,累加量递增形成等差数列求和 → 。
【2025年408真题】 下面程序段的时间复杂度是()。
int count = 0;
for (int i = 1; i * i <= n; i++)
for (int j = 1; j <= i; j++)
count++;
A. B. C. D.
解:外层循环 从1到 ,内层循环 从1到 。总执行次数 。答案选B,。
⚠️ 相关的时间复杂度是近年真题高频考点! 常见模式:
- 循环条件含
i*i<=n或sum<n(sum等差递增)→ 或- 2019真题:条件
n>=(x+1)*(x+1)同理 →
【2014年408真题】 下面程序段的时间复杂度是()。
int count = 0;
for (int k = 1; k <= n; k *= 2)
for (int j = 1; j <= n; j++)
count++;
A. B. C. D.
解:外层 每次乘2,执行 次;内层 每次执行 次。总次数 。答案选B。
💡 解题套路:外层"乘2"→ 次;内层"加1"→ 次。相乘即得 。
【2019年408真题】 下面程序段的时间复杂度是()。
int x = 0;
while (n >= (x + 1) * (x + 1))
x++;
A. B. C. D.
解:循环执行 次后 ,退出条件 ,即 ,所以 。答案选B。
💡 解题套路:循环条件含 ,等价于 从0增长到 ,时间复杂度为 。
【2022年408真题】 下面程序段的时间复杂度是()。
int count = 0;
for (int i = 1; i <= n; i *= 2)
for (int j = 1; j <= i; j++)
count++;
A. B. C. D.
解:外层 (其中 ),内层执行 次。总次数:
这是等比数列求和,结果为 。答案选B。
⚠️ 易错点:不要想当然地认为"外层 × 内层 "就是 。内层循环次数随外层变化(,不是 ),必须用等比求和精确计算。这是区分 和 的高频陷阱!
📌 第一章总结
| 核心知识点 | 考研要求 |
|---|---|
| 数据、数据元素、数据项、数据对象的概念层次 | 理解概念,区分层次 |
| 逻辑结构四种类型(集合、线性、树形、图状) | 必须牢记 |
| 存储结构四种方式(顺序、链式、索引、散列) | 必须牢记,理解各自特点 |
| 算法五大特性(有穷、确定、可行、输入、输出) | 必须牢记,区分算法与程序 |
| 时间复杂度分析 | 重点! 必须会分析代码的时间复杂度 |
| 常见复杂度大小排序 | 必须牢记 |
| 空间复杂度分析 | 理解概念,注意"原地工作"含义 |