GESP6级集训课第1讲
一、线性数据结构
1. 栈
1.1 概念
栈:只能在某一端插入和删除的线性数据结构(和羽毛球筒一样)
栈顶:进行删除和插入的一端
栈底:不进行删除和插入的一端
进栈:放入元素
出栈:取出元素
满栈:栈的元素填满整个栈
空栈:栈中无元素
栈的特点:先进后出,后进先出(FILO)
下溢:空栈时出栈
上溢:满栈时进栈
1.2 程序
1.2.1 STL
头文件:
#include<stack>
定义格式:
stack<数据类型>栈名;
基本操作:
方法 | 功能 |
---|---|
.push(x) | x x x 进栈 |
.pop() | 出栈 |
.top() | 栈顶元素 |
.empty() | 判空 |
.size() | 元素个数 |
1.2.2 数组模拟
定义格式:
const int N=100;//栈的大小
int top=0;//栈顶指针
数据类型 s[N+1];
基本操作:
方法 | 功能 |
---|---|
s[++top]=x | x x x 进栈 |
top-- | 出栈 |
s[top] | 栈顶元素 |
1.2.3 链式栈
概念:
链栈(链式栈)是一种基于链式存储结构(单链表)实现的栈。与顺序栈不同,链式栈的栈顶指针指向链表的头部。由于栈的操作仅限于栈顶,因此栈顶放在单链表的头部。在链栈中,栈顶元素的插入和删除操作都在链表的头部进行。
基本操作:
- 初始化栈:创建一个空链式栈,栈顶指针指向空
- 进栈:在链表头部插入新结点
- 出栈:删除链表头部结点
- 栈顶元素:返回链表头部的结点数据
- 判空:判断栈顶指针是否为空
1.3 应用
1.3.1 括号配对
具体方法:
- 遍历表达式中每个字符,遇到左括号时入栈
- 遇到右括号时与栈顶元素匹配并出栈
- 若匹配成功则继续,若不匹配或栈空则表示括号不匹配
- 遍历结束后,若栈为空则括号匹配正确,否则错误
1.3.2 进制转换
具体方法:
- 将十进制数除以要转的进制,得到商和余数
- 将余数进栈
- 重复前两步直到商为 0 0 0
- 依次出栈,得到的序列为最终表示结果
利用原理:因为最终时倒取余,可以用栈的后进先出特性来解决
1.3.3 前缀表达式求值
具体方法:
- 从右到左扫描
- 遇数进栈
- 遇号将栈顶两个元素计算,运算结果放入栈顶(栈顶元素 操作符 第二个元素)
1.3.4 后缀表达式求值
具体方法:
- 从左到右扫描
- 遇数进栈
- 遇号将栈顶两个元素计算,运算结果放入栈顶(第二个元素 操作符 栈顶元素)
2. 队列
2.1 概念
队列:先进先出的先行数据结构,允许在一段插入,另一端删除(和排队一样)
队头:离开队列的一端
队尾:进入队列的一端
上溢:队列满继续入队
假上溢:队列存在可用位置时发生的上溢
2.2 程序
2.2.1 STL
头文件:
#include<queue>
定义格式:
queue<数据结构>队列名;
基本操作:
方法 | 功能 |
---|---|
.push(x) | x x x 入队 |
.pop() | 出队 |
.front() | 队头元素 |
.back() | 队尾元素 |
.empty() | 判空 |
.size() | 元素个数 |
2.2.2 数组模拟
定义格式:
const int N=100;//队列的大小
int head=0,tail=0;//队头队尾指针,tail指向哨兵位置
数据类型 q[N+1];
基本操作:
方法 | 功能 |
---|---|
s[tail++]=x | x x x 入队 |
head++ | 出队 |
tail-head | 元素个数 |
tail==head | 判空 |
2.3 应用
2.3.1 循环队列
概念:能够循环利用数组空间的队列
基本操作:
方法 | 功能 |
---|---|
q[tail]=x,tail=(tail+1)%N | x x x 入队 |
head=(head+1)%N | 出队 |
(tail-head+N)%N | 元素个数 |
tail==head | 判空 |
(tail+1)%N==head | 判满 |
二、树形数据结构
1. 树的概念
树:一种抽象的非线性数据结构,用来模拟具有树状结构性质的数据集合
树的特点:由
n
n
n(
n
≥
0
n\ge0
n≥0)个结点组成的具有分支和层次特性的数据集合。
直接前驱:父结点
直接后继:子结点
根结点:没有直接前驱,只有直接后继的结点
叶结点:度为
0
0
0 的结点
兄弟结点:具有相同父结点的结点
层次:从根开始,根为第
1
1
1 层,根的子结点为第
2
2
2 层,以此类推
深度:根结点深度为
0
0
0 或
1
1
1,根的子结点为父结点的深度
+
1
+1
+1
子树:以树中某一个结点为根,该结点和其所有后代结点构成的一棵树
森林:
m
m
m(
m
≥
0
m\ge0
m≥0)棵互不相交的树的集合
结点的度:拥有的子树个数
树的度:一棵树中所有结点的度的最大值为树的度
树是一种特殊类型的无环连通图,任何两个顶点之间都有一条唯一的简单路径。
结点的权:人为赋予的一个数,通常代表结点的重要性
结点的带权路径长度:从根结点到该结点的路径长度与该结点权重的乘积
数的带权路径长度:树中所有叶结点的带权路径长度之和(WPL
,Weighted Path Length
)
2. 树的性质
- 树中的结点数等于所有结点的度之和
+
1
+1
+1,即
n = 1 + 1 × n 1 + 2 × n 2 + ⋯ n=1+1\times n_1+2\times n_2+\cdots n=1+1×n1+2×n2+⋯ - 树中的结点数等于所有结点个数总和,即
n = n 0 + n 1 + n 2 + ⋯ n=n_0+n_1+n_2+\cdots n=n0+n1+n2+⋯ - n n n 个结点的树中有 n − 1 n-1 n−1 条边
3. 二叉树
3.1 概念
二叉树(binary tree
,BT)是一种特殊的树形结构,二叉树的每个结点最多有两个子结点,每个结点的子结点分别成为左孩子、右孩子,它的两棵子树分别成为左子树、右子树。
3.2 形态
- 空二叉树
- 只有根结点
- 只有左子树
- 左右子树都有
- 只有右子树
3.3 性质
- 非空二叉树的叶结点数等于度为
2
2
2 的结点数
+
1
+1
+1,即
n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1 - 非空二叉树第 k k k 层上至多有 2 k − 1 2^{k-1} 2k−1 个结点( k ≥ 1 k\ge1 k≥1)
- 高度为 h h h 的二叉树之多有 2 h − 1 2^h-1 2h−1 个结点( h ≥ 1 h\ge1 h≥1)
3.4 特殊二叉树
3.4.1 满二叉树
满二叉树:一棵高度为 h h h 且有 2 h − 1 2^h-1 2h−1 个结点的二叉树。
约定根结点编号为 1 1 1,自上而下,自左向右编号。对于编号为 i i i 的结点,若有父结点则其父结点编号为 i 2 \frac{i}{2} 2i,若有左孩子则左孩子为 2 i 2i 2i,若有右孩子则右孩子为 2 i + 1 2i+1 2i+1。可以使用数组来存储一颗满二叉树。即
3.4.2 完全二叉树
完全二叉树:一棵高度为 h h h、有 n n n 个结点,且所有结点都与相同高度的满二叉树编号对应时的二叉树。所以也可以使用数组来存储一颗完全二叉树。
特点:
- 叶结点只可能在层次最大的两层上出现,对于最大层次中的叶结点,都依次排列在该层最左边的位置上。
- 若有度为 1 1 1 的结点,则只可能有一个,且该结点只有左孩子,没有右孩子
性质:
- 按层编号后,一旦出现某结点(编号为 i i i)为叶结点或只有左孩子时,则编号大于 i i i 的结点均为叶结点
- 若 n n n 为奇数则没有度为 1 1 1 的结点,叶结点数量等于 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil ⌈2n⌉;若 n n n 为偶数则编号为 n 2 \frac{n}{2} 2n 的结点只有左孩子没有右孩子,叶结点数量等于 n 2 \frac{n}{2} 2n
4. 哈夫曼树
4.1 定义
在含有
n
n
n 个带权叶结点的二叉树中,WPL
最小的二叉树被成为哈夫曼树,也称最优二叉树。
4.2 构造
算法原理:贪心算法
初始状态:对于给定的
n
n
n 个权值,每个权值作为一个独立的结点,形成
n
n
n 个单结点树。
构造过程:
- 从当前的森林中选取两个根结点权值最小的树(或结点)。
- 将这两个树合并为一棵新的二叉树,新树的根结点的权值为两个子树根权值之和。
- 将新树放回森林中,同时删除原来两个独立的树。
- 重复上述步骤,直到森林中只剩下一棵树为止。
最终结果:剩下的那棵树就是哈夫曼树,其叶结点所代表的权值对应着原始输入的各个权重,并且树的带权路径长度达到最小。
4.3 性质
- 每个初始结点都会成为哈夫曼树的叶结点,且权值越小的结点到根结点的路径长度越大;
- 哈夫曼树的总结点数为 2 n − 1 2n-1 2n−1
- 哈夫曼树中不存在度为 1 1 1 的结点
- 哈夫曼树不唯一,但
WPL
必然相同且最小
4.4 哈夫曼编码
4.4.1 性质
- 哈夫曼编码是一种无损编码
- 哈夫曼编码是变长码
- 哈夫曼编码基于频率排序实现
- 哈夫曼编码是最优前缀码
- 哈夫曼编码不是唯一码
4.4.2 原理
让出现频率高的信息编码长度短,让出现频率低的信息编码长度长,从而达到哈夫曼编码整体最短。因此哈夫曼编码使用的是贪心算法的思想。
4.4.3 方法
- 统计待编码的字符出现的频率,并排列。
- 反复取出两个频率最低的节点,将它们合并为一个新节点,频率为两个节点频率之和,将新节点重新排列。
- 重复步骤,直到只剩下一个节点,即构建完成的哈夫曼树。
- 通过遍历哈夫曼树,从根节点到每个叶子节点的路径上的左子树赋值为 0 0 0,右子树赋值为 1 1 1,得到每个字符的哈夫曼编码。
- 使用哈夫曼编码将原始数据进行编码和解码。