目录
1 绪论
1.1 数据结构
数据:信息的载体;
数据元素:数据的基本单位,一个数据元素由若干数据项组成;
数据项:数据不可分割的最小单位;
数据对象:具有相同性质的数据元素的集合,是数据的一个子集;
数据类型:一个值的集合和定义在此集合上的一系列操作的总称;
数据结构:相互之间存在一种或多种特定关系的数据元素的集合;
1.2 数据结构的三要素
1.2.1 逻辑结构
主要分为线性结构(线性表、栈、队列)和非线性结构(图、树、集合)
1. 集合 —— 同属一个集合
2. 线性结构 —— 一对一
3. 树形结构 —— 一对多
4. 图状结构或网状结构 —— 多对多
1.2.2 物理结构(存储结构)
定义 | 优点 | 缺点 | |
---|---|---|---|
顺序存储 | 按顺序存储 | 可随机存取;占用空间少; | 只能使用相邻的一整块存储单元;产生较多外部碎片; |
链式存储 | 借助指示元素存储地址的指针 | 不会出现碎片现象;充分使用存储单元; | 存储指针占用额外空间;只能顺序存取;不同节点存储空间不连续;同一节点内的存储空间要连续; |
索引存储 | 建立附加索引表 | 检索速度快 | 附加索引表额外占用空间;增加和删除数据时也要修改索引表,花费时间较多; |
散列存储 | 根据元素关键字直接计算出元素的存储地址,又称哈希存储 | 检索、增加和删除节点的操作都很快 | 可能出现元素存储单元的冲突;解决冲突又增加时空开销; |
1.2.3 数据的运算
包括运算的定义和实现
运算的定义是针对逻辑结构的,指出运算的功能
运算的实现是针对存储结构的,指出运算的具体操作步骤
时间复杂度
空间复杂度
2 线性表
线性表的顺序表示(数组)
(1)静态分配
(2)动态分配
线性表的链式表示(链表)
(1)单链表
(2)双链表
(3)循环链表:最后一个结点指向头结点,可以从任意一个节点开始遍历整个链表
(4)静态链表(借助数组形式实现)
3 栈
- 仅允许在栈顶进行插入、删除
- 先进后出
顺序栈
栈顶指针:初始设置 – S.top = -1
进栈操作:栈不满,S.top ++,S.data[S.top] = e
出栈操作:栈非空,e = S.data[S.top],S.top –
栈空条件:S.top == -1
栈满条件:S.top == Maxsize - 1;
共享栈
3.1 栈的链式存储结构
单链表实现
3.2 栈的应用
括号匹配;
表达式求值;
递归;
4 队列
- 允许队头删除,队尾插入
- 先进先出
顺序队列
初始状态:Q.front == Q.rear == 0(front-对头指针,rear-队尾指针)
进队操作:队不满,Q.rear = e,Q.rear++
出队操作:队不空,e = Q.front,Q.front++
不能使用 Q.rear == MaxSize 判断是否队满
循环队列不能使用 Q.front == Q.rear 判断是否队空
双端队列
允许两端都可以进行入队和出队操作的队列
4.1 队列的链式存储结构
4.2 队列应用
层次遍历;
页面替换算法(FIFO);
解决由多用户引起的资源竞争问题(CPU资源竞争问题);
解决主机与外部设备之间速度不匹配的问题(如打印机与主机,设置一个打印数据缓冲区);
5 树
定义:树是一种数据结构,它是由n个有限节点组成一个具有层次关系的集合
特点:每个节点有零个或多个子节点;没有父节点的节点称为根节点;
树的高度 = 根节点到叶子节点的最长路径
节点的深度 = 根节点到这个节点所经历的边数
节点的层次 = 节点的深度 + 1
** 度 **:树中一个节点的孩子个数称为该节点的度。所有节点的度的最大值是树的度。
5.1 树的存储结构
双亲表示法 | 孩子表示法 | 孩子兄弟表示法 |
---|---|---|
采用一组连续空间存储每个节点; 在每个节点中设置一个伪指针; 伪指针指示其双亲节点在数组中的位置; | 将每个节点的孩子节点用单链表表示 | 又叫二叉树表示法; 以二叉链表作为树的存储结构; 节点内容包含孩子节点、数据、兄弟节点三个部分; |
![]() | ![]() | ![]() |
5.2 二叉树
特殊二叉树
满二叉树 | 完全二叉树 | 二叉排序树 | 平衡二叉树 |
---|---|---|---|
高度 = h 节点数 = 2 h − 1 2^h - 1 2h−1 | 叶子节点只能出现在最下层和次下层; 一棵满二叉树必定是一棵完全二叉树; 完全二叉树中,度为1的节点数仅可能为0个或1个; | 左子树节点比根节点值小; 右子树节点比根节点值大; 没有键值相等的节点; | 树上任一结点的左子树和右子树的深度之差不超过1 |
5.2.1 二叉树的遍历方法
前序遍历 | 中序遍历 | 后序遍历 | 层序遍历 | |
---|---|---|---|---|
过程 | 根节点 -> 左节点 -> 右节点 | 左节点 -> 根节点 -> 右节点 | 左节点 -> 右节点 -> 根节点 | 从上至下,从左至右 |
递归代码 | ![]() | ![]() | ![]() | ------ |
非递归代码 | ![]() | ![]() | ![]() | ![]() |
例子:
前序遍历结果: 3 1 2 5 4 6
中序遍历结果: 1 2 3 4 5 6
后序遍历结果: 2 1 4 6 5 3
5.3 哈夫曼树
定义:树的带权路径最小的二叉树;
WPL = 路径长度 * 节点权值
特点:
- 没有度为1的节点;
- n个叶节点的哈夫曼树,共有2n-1个节点;
- 哈夫曼树的任意非叶节点的左右子树交换后仍是哈夫曼树;
- 同一组权值,可能存在不同的哈夫曼树;
- 哈夫曼树不一定是完全二叉树
哈夫曼编码
- 出现频率越高的字符越在上层,这样它的编码越短;
- 出现频率越低的字符越在下层,
6 图
- 图 = 顶点 + 边
- 无向图边数 * 2 = 各顶点度数之和;
- 有向图边数 = 各顶点的入度之和 = 各顶点的出度之和;
- 一个连通图的生成树是一个极小连通子图,无环;
- 完全无向图:边数 = n ( n − 1 ) 2 \frac{n(n - 1)}{2} 2n(n−1),< n = 顶点数 >
- 完全有向图:边数 = n ( n − 1 ) {n(n - 1)} n(n−1)
- 简单路径:顶点不重复出现的路径
- 距离:从u到v的距离 = 从u到v的最短路径长度
6.1 图的存储方式
邻接矩阵 | 邻接表 |
---|---|
用两个数组表示图: 一个一维数组存储图中顶点信息; 一个二维数组存储图中的边或弧的信息; | 顶点用一个一维数组存储; 每个顶点的所有邻接点构成一个线性表; |
无向图邻接矩阵特点: 对称矩阵,可压缩存储; 节点的度 = 行/列的非零个数 有向图邻接矩阵特点:节点的度 = 行 + 列的非零个数 | 有向图分入度和出度 |
![]() | ![]() |
6.2 图的遍历方法
深度优先搜索DFS | 广度优先搜索BFS | |
---|---|---|
定义 | 相当于树的先序遍历; 根节点就是任意出发的节点; 子节点就是所有邻近的未访问过的节点; | 相当阿玉树的层序遍历 |
遍历结果 (从2开始遍历) | 2-1-5-6-3-4-7-8 | 2-1-6-5-3-7-4-8 |
生成树 (从2开始遍历) | ![]() | ![]() |
6.3 最小生成树
- 图的所有生成树中边的权值之和最小的树;
- 最小生成树不唯一;
- 当图的各边权值都不相等,最小生成树唯一;
- 当图本身是一棵树,最小生成树就是它本身;
- 最小生成树的边数 = 顶点数 - 1
Prim算法
每次从已选择的节点所含边中选择最小的边连接的点(注:该点需要是未被使用过的点)
Kruskal算法
每次从不在同一个连通分量中的点所含边中选择最小的边
6.4 最短路径
BFS广度优先搜索算法 —— 无权图
Dijkstra算法 —— 有向图
从起点出发,首先找出能从起点直达未标记顶点的最短路径,并将改顶点标记,随后每次从起点开始通过已标记点到达未标记顶点的所有路径中选择最短的路径,并标记连接的顶点,知道图连通。
Floyd算法 —— 任意两顶点
详细过程示例
6.5 关键路径
最早开始时间,从起点到各顶点的最大路径;
最晚开始时间,从终点回退,减去前一条边,取最小值;
A | B | C | D | E | F | G | H | I | J | K | |
---|---|---|---|---|---|---|---|---|---|---|---|
最早开始时间 | 0 | 3 | 5 | 15 | 7 | 4 | 11 | 9 | 21 | 22 | 28 |
最晚开始时间 | 0 | 6 | 15 | 21 | 7 | 4 | 11 | 19 | 21 | 22 | 28 |
7 排序
9 3 1 4 2 7 8 6 5
最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 | 内外排序 | 数据对象 | 其他 | 排序过程 | |
---|---|---|---|---|---|---|---|---|---|
直接插入排序 | O(n) | O( n 2 n^2 n2) | O( n 2 n^2 n2) | O(1) | 稳定 | 内排序 | 数组、链表 | 在最后一趟开始前, 所有元素可能都不在最终位置; 待排序序列基本有序的情况下,该方法效率最高; 最坏情况比较次数= n × ( n − 1 ) 2 \frac{n×(n-1)}{2} 2n×(n−1); 最好情况比较次数= n-1; | ![]() |
希尔排序 | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O(1) | 不稳定 | 内排序 | 数组 | — | ![]() |
冒泡排序 | O(n) | O( n 2 n^2 n2) | O( n 2 n^2 n2) | O(1) | 稳定 | 内排序 | 数组 | 每一趟最后一个元素都是最大的元素(目标顺序:从小到大); 元素从大到小时 = 最坏情况比较次数= n × ( n − 1 ) 2 \frac{n×(n-1)}{2} 2n×(n−1); 元素从小到大时 = 最好情况比较次数= n-1; | ![]() |
快速排序 | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n 2 n^2 n2) | O( l o g 2 n log_2n log2n) | 不稳定 | 内排序 | 数组 | 当数据随机或者数据量很大的时候,适合快速排序; 当排序的数据已基本有序,不适合快速排序 | ![]() |
简单选择排序 | O( n 2 n^2 n2) | O( n 2 n^2 n2) | O( n 2 n^2 n2) | O(1) | 不稳定 | 内排序 | 数组、链表 | — | ----- |
堆排序 | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O(1) | 不稳定 | 内排序 | 数组 | 取一大堆数据中的k个最大(最小)的元素时,都优先采用堆排序; 可以将堆视作一颗完全二叉树,采用顺序存储方式保护堆; 插入和删除一个新元素的时间复杂度都为 O( l o g 2 n log_2n log2n); 构造n个记录的初始堆,时间复杂度为O(n) ; | ![]() |
归并排序 | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O( n l o g 2 n nlog_2n nlog2n) | O(n) | 稳定 | 外排序 | 数组、链表 | 分阶段可以理解为就是递归拆分子序列的过程,递归深度为log_2n空间复杂度为O(n); 比较次数数量级与序列初始状态无关; 对于N个元素进行k路归并排序排序的趟数满足 k m = N k^m=N km=N | ![]() |
基数排序 | O(d(n+r) | O(d(n+r) | O(d(n+r) | O® | 稳定 | 外排序 | 数组、链表 | 通常基数排序第一趟按照个位数字大小,第二趟按照十位数字大小; MSD是最高位优先,LSD是最低位优先; 基数排序不能对float和double类型的实数进行排序; | ----- |