1. 绪论
1.1 基本概念
基本概念和术语- 数据
- 数据元素 一个对象,比如一个学生就是一个数据元素,由学号、姓名、性别等数据项组成。
- 数据对象 具有相同性质的数据元素的集合
- 数据类型
数据结构三要素
- 逻辑结构 ,
分为线性结构(结构中的数据元素之间只存在一对一的关系)和非线性结构。 - 存储结构
1)顺序存储(逻辑上相连的元素存储在物理位置上也相连的存储单元中)
2)链式存储
3)索引存储
4)散列存储 - 运算
1.2 算法
一个算法具有以下5个重要特性:(必须满足)- 有穷性
- 确定性
- 可行性
- 输入
- 输出
1.3 例题
1.【真题】 已知两个长度分别为m,n的升序链表,若将他们合并为长度为m+n的一个降序链表,则最坏情况下的时间复杂度为()A.O(n) B.O(mn) C.O(min(m,n)) D.O(max(m,n))
答案: D
2.下列说法错误的是()
I. 算法原地工作的含义是指不需要任何额外的辅助空间
II. 在相同规模n下,复杂度为O(n)的算法在时间上总是优于复杂度为O(2 n)的算法
A. I B.II C. I和II
答案: A
2. 线性表
顺序表 | 线性表 | |
---|---|---|
特点 | 表中元素的逻辑顺序与物理顺序相同 | |
存取方式 | 随机存取(存取表中元素时可以随机存取任一元素) | 非随机存取 |
优点 | 1.存取速度快O(1) 2.存储密度大,需要大量连续存储单元 | 1.插入O(1)和删除方便O(n) (删除主要浪费的时间在查找上) 2.结点空间只需要在申请时分配,操作灵活高效 |
缺点 | 1.插入删除难O(n) 2.需要预分配足够大的存储空间 | 查找O(n) |
2.1 顺序表
2.2 链式表
2.3 例题
1.【真题】已知一个带有表头结点的单链表,结点结构为 【data|link】假设该链表只给出了头指针list,在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,则输出data值,返回1;否则只返回0。
思路:定义两个指针p,q初始时均指向头结点的下一个结点,p沿链表移动,当p移动到第k个结点时,q指针开始与p指针同时移动。当p指针移动到最后一个结点时,q指针所指示结点为倒数第k个结点。
3. 栈和队列
栈和队列是操作受限的线性表。
3.1 栈
先进后出数学性质: n个不同元素进栈,出栈元素不同排列的个数为Cn2n /(n+1)
3.1.1 共享栈
为了更好地利用存储空间,防止上溢3.2 队列
先进先出3.2.1 循环队列
队空: Q.front = Q.rear入队: Q.rear = (Q.rear + 1) % MaxSize
出队: Q.front = (Q.front + 1) % MaxSize
元素个数: (Q.rear - Q.front + MaxSize) % MaxSize
区别队空还是队满:
- 牺牲一个单元 (Q.rear + 1) % MaxSize = Q.front
3.2.2 双端队列
3.3 例题
1.【真题】已知循环队列存储在一维数组A[0…n-1]中,且队列非空时front和rear分别指向队头元素和队尾元素,若初始时队列为空,且要求第一个进入队列的元素存储在A[0]处,则初始front和rear的值分别是()A.0,0 B.0,n - 1 C.n-1, 0 D. n-1, n-1
解答:因为队列非空时front和rear要指向队头元素和队尾元素,所以在入队后front和rear都要指向A[0],且入队只操作rear = (rear + 1) % n,所以选B
2. 用链式存储方式的队列进行删除操作时需要
A. 仅修改头指针 B.仅修改尾指针 C.头尾指针都要修改 D.头尾指针可能都要修改
解答: 删除元素从表头删除,通常仅需要修改头指针,但若队列中仅有一个元素时,尾指针也需要修改,删除后队列为空,需修改尾指针rear = front,选D
4. 串
4.1 KMP算法
KMP算法分为两步
- 获取next数组
- 根据next数组对子串进行移动
4.1.1 next数组
next[j] =- 0, j = 1 (模式串中第一个字符(j=1))
- s的最长相等前后缀长度+1, 前1~j-1个字符记为s
例如: ‘ababaa’
j | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
a | b | a | b | a | a | |
next[j] | 0 | 1 | 1 | 2 | 3 | 4 |
void get_next(SString T,int next[] )
{
int i = 1, j = 0;
next[1] = 0;
while(i < T.length)
{
if(j == 0 || T.ch[i] == T.ch[j])
{
i++;
j++;
next[i] = j;
}else j = next[j];
}
}
4.1.2 kmp算法
两个指针p=1,q=1初始指向主串S和子串L的第一个字符,若字符匹配则p++,q++, 若不匹配则子串指针q发生回退,q = next[q]。最终当出现q>L.length时找到匹配子串的第一个字符的位置i = q - L.lenthint index_KMP(SString S, SString L) // S大字符串,L要查找的字符串
{
int i = 1,j = 1;
int next[L.length+1];
get_next(L, next);
while(i <= S.length && j <= L.length)
{
if(j == 0 || S.ch[i] == L.ch[j])
{
i++;
j++;
}else j = next[j];
}
if(j > L.length) return i - L.length;
return 0;
}
4.1.3 kmp算法的进一步优化
当出现子串L中的字符L[i] = L[j]时(j > i),next[j] = next[i]例如: 'aaaab'
j | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
a | a | a | a | b | |
next[j] | 0 | 1 | 2 | 3 | 4 |
nextval[j] | 0 | 0 | 0 | 0 | 4 |
void get_nextval(SString T,int next[] )
{
int i = 1, j = 0;
next[1] = 0;
while(i < T.length)
{
if(j == 0 || T.ch[i] == T.ch[j])
{
i++;
j++;
if(T.ch[i] != T.ch[j]) next[i] = j;
else next[i] = next[j];
}else j = next[j];
}
}
4.2 例题
1.【真题】设主串T为'abaabaabcabaabc',模式串S='abaabc',采用KMP算法进行模式匹配,到匹配成功为止,在匹配过程中进行的单个字符间的比较次数为______解答:
j | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
a | b | a | a | b | c | |
next[j] | 0 | 1 | 1 | 2 | 2 | 3 |
- 第一步比较了6次,此时在主串的第6个字符,模式串的第6个字符,发生了不匹配,根据next[6] = 3,模式串的指针j跳转到了3
- 第二步从主串的第6个字符‘a’和模式串的第3个字符’a’开始进行匹配,匹配了4次,成功找到子串。
所以答案为10次。
5.树与二叉树
5.1 树的定义
- 树中一个结点的孩子个数称为该结点的度,树中结点最大度数称为树的度
- 结点的深度是从根结点开始自顶向下逐层累加的
- 结点的高度是从叶子结点开始自底向上逐层累加的
- 结点个数的计算,若树的度为2,n = n0+n1+n2,且n = 0n0+1n1+2*n2+1,因为所有结点的度+1 = 所有结点个数(根节点没有父结点)
5.2 二叉树
二叉树是有序树完全二叉树
- 若有度为1的结点,则只可能有一个,且该结点只有左孩子无右孩子
- 非空二叉树的叶子结点数等于度为2的结点数加1,即n0=n2+1
证明:设B为分支总数,n为总结点数, n = B + 1, B = n1 + 2n2,n = n0 + n1 + n2 - 具有n个结点的完全二叉树的高度为⌈log2(n+1)⌉
证明:2h-1<n <= 2h -1
5.2.1 二叉树的存储结构
顺序存储结构:适合满二叉树和完全二叉树一般从数组下标1开始存储树中的结点
链式存储结构:在含有n个结点的二叉链表中,含有n+1个空链域
5.2.2 二叉树的遍历
- 先序遍历: 根左右
- 中序遍历: 左根右
- 后序遍历: 左右根
5.3.3 *线索二叉树
引入线索二叉树的目的就是为了加快查找结点前驱和后继的速度。将含有n个结点的二叉树中的n+1个空指针用于存放线索。
线索化的过程中若结点没有前驱或者后继也要指向null
中序线索化
最后一个结点的右孩子指针指向null,rtag=1,用于遍历时判断结点有无右孩子,且中序遍历的最后一个结点一定没有右孩子。
先序线索化
先序线索化时由于先对T结点进行了线索化,当PreThread(T->lchild)时会出现,访问T结点的前驱,出现循环,所以要访问T的左孩子结点时要对rtag是否是真的左孩子的判断。(右孩子不用)
后续线索化
线索二叉树中寻找前驱后继(一般只考察手算)
I.T.rtag == 1,next = T->rchild
II.T.rtag == 0,next = p的右子树的最左下结点
- 前驱
先序线索二叉树
- 后继
- 前驱,若有左孩子,则找不到
后序线索二叉树
- 后继,若有右孩子,则找不到
- 前驱
5.3 树和森林
5.3.1 树和森林的转换
树的性质:n个结点的树中有n-1条边, 若森林F中有15条边,25个结点,说明森林中有(25-15)=10颗树树转换为二叉树的规则:每个结点左指针指向它的第一个孩子,右指针指向它的相邻右兄弟
5.3.2 树和森林的遍历
树 | 森林 | 树对应的二叉树的遍历序列 |
---|---|---|
先根遍历 (如上图的先根遍历序列为 ABEFCDG) | 先序遍历森林 (依次对各个树先根遍历) | 先序遍历 |
后根遍历 (EFBCGDA) | 中序遍历森林 (依次对各个树后根遍历) | 中序遍历 |
后序遍历 |
5.4 二叉排序树(BST)
5.4.1 二叉排序树的构造
5.4.2 二叉排序树的删除
若被删除结点Z为
- 叶结点,直接删除
- 结点只有一颗左子树或右子树,让子树成为Z的父结点的子树
- 有左右两棵子树,令Z的直接后继(或直接前驱)代替Z,然后从二叉排序树中删去这个直接后继,转化为情况1.2
5.4.3 二叉排序树的查找效率
当有序表是静态查找表时,宜用顺序表作为其存储结构,而采用二分查找表实现其查找操作;
当有序表时动态查找表,应选择二叉排序树作为其逻辑结构。
查找成功的平均查找长度ASLa=(1+2x2+3x4+4x3) / 10 = 2.9
查找失败的平均查找长度ASLa=(3x5+4x6) / 11 = 3.55
5.4.4 *平衡二叉树 AVL树(一般考察选择题,手算)
平衡因子 = 左子树高 - 右子树高叶子结点没有平衡因子。 定义: 平衡二叉树的平衡因子只可能是0,-1,1
-
LL
-
RR
-
LR,令最小平衡子树的根节点的左孩子的右孩子先左旋再右旋
-
RL
5.5 哈夫曼树
结点的带权路径长度=从树的根结点到该结点的路径长度(经过的边数)与该结点上权值的乘积
树的带权路径长度WPL=树中所有带权路径长度之和
5.5.1 哈夫曼树的构造
- 从权值最小的两个结点
5.6 例题
1.若一颗深度为6的完全二叉树的第6层有3个叶子结点,则该二叉树中共有____个叶子结点。解答: 第5层共有16个结点,减去有孩子的2个结点,共有14个+3个=17个
2.已知一颗有2011个结点的树,其叶结点个数是116,该树对应的二叉树中无右孩子的结点个数是_____。
解答: 所以答案为1896
3. 线索二叉树是一种__结构
A.逻辑 B.逻辑和存储 C.物理 D.线性
答案:线索二叉树是加上线索的链表结构,是二叉树在计算机内部的一种存储结构,选C
4. __的遍历仍需要栈的支持
A.前序线索树 B.中序线索树 C.后序线索树 D.所有线索树
解答:后序线索树最后访问根结点,后序线索树不方便访问后继结点,所以还是要用递归即用到栈才能完全遍历。选C
5.【真题】若一棵非空k(k>2)叉树T中的每个非叶结点都有k个孩子,则称T为正则k叉树。请回答:
1)若T有m个非叶结点,则T中的叶结点有多少
2)若T的高度为h(单结点的树高为1),则T的结点数最多为多少个?最少为多少个?
解答:1)B+1 = k*m = m + n0 => n0 = (k-1)m + 1
2) 最多:(1-kh )/(1-k)。 最少 1 +(h-1)k
6.【真题】若将关键字1,2,3,4,5,6,7依次插入初始为空的平衡二叉树T,则T中平衡因子为0的分支结点的个数是 ____。
解答:
所以是3个。
6. 图
线性表可以是空表,树可以是空树,但图不可以是空图。图中不能一个顶点也没有,图的顶点集一定非空,但边集可以为空。概念:
有向边<v,w>称为从v到w的弧,v称为弧尾,w为弧头。
简单图:
- 不存在重复边
- 不存在顶点到自身的边
连通图: 无向图中,若从顶点v到w有路径存在,则称v和w是连通的,若图G中任意两个顶点都是连通的,则称图G为连通图。无向图中的极大连通子图称为连通分量。
若一个图有n个顶点,如果边数小于n-1,那么此图必是非连通图。
非连通图最多可以有C2 n-1条边。
强连通图: 有向图中,若从顶点v到w和w到v都有路径存在,则称v和w是强连通的。若图中任意一对顶点都是强连通的,则称此图为强连通图。有向图中的极大强连通子图称为有向图的强连通分量。
生成树: 连通图的生成树是包含图中全部顶点的一个极小连通子图。
6.1 图的存储
V为图的顶点数,E为图的边数
邻接矩阵 | 邻接表 | |
---|---|---|
确定图中的边数 | 按行按列对每个元素进行检测,时间花费大 | |
删除顶点 | 方便 | 复杂度高 |
适合图 | 稠密图 | 稀疏图 |
空间复杂度 | O(v2) | 无向图: O(V+E) 有向图:O(V+2E) |
唯一性 | 唯一 | 不唯一 |
十字链表
有向图的一种链式存储结构
顶点结构:firstIn、firstOut分别指向以该顶点为弧头或弧尾的第一个弧结点
弧结点:尾域tailvex和头域headvex分别指示弧尾和弧头这两个顶点在图中的位置。链域hlink、tlink分别指向弧头相同和弧尾相同的下一条弧
邻接多重表:无向图的另一种链式存储结构
顶点:
- data:存储此顶点的数据;
- firstedge:指针域,用于指向第一条依附于该结点的边。
边:
- mark:标志域,用于标记此结点是否被搜索过
ivex 和 jvex:数据域,分别存储图中各边两端的顶点所在数组中的位置下标;
ilink:指针域,指向下一个依附于 顶点ivex 的边 ;
jlink:指针域,指向下一个依附于 顶点jvex 的边;
info:指针域,用于存储与该顶点有关的其他信息,比如无向网中各边的权;
6.2 图的遍历
广度优先遍历BFS:先访问起始顶点v,从v出发,依次访问v的各个未被访问过的邻接顶点w1,w2,…,wi,再依次访问w1,w2,…wi的未被访问的邻接结点。(队列实现)
广度优先生成树
因此访问顺序是:A -> B -> C -> D -> F -> G -> E -> H
深度优先遍历DFS:先访问起始顶点v,从v出发,访问v的未被访问过的邻接顶点w1,再访问w2,的未被访问的邻接结点,重复上述过程,当不能再继续向下访问时,依次退回最近被访问的结点,若它还有没被访问过的邻接结点,重复上述过程。
因此访问顺序是:A -> B -> G -> E -> C -> D -> H -> F
6.2.1 图的遍历与连通性
- 无向图:调用BFS/DFS函数次数 = 连通分量数
- 有向图:对强连通图,只调用1次函数
6.3 最小生成树
最小生成树: 带权图中权值之和最小的生成树。
6.3.1 Prim算法
时间复杂度:O(V2),适用于边稠密的图
初始时从图中任取一个顶点如V1加入树T,之后选择一个当前T中顶点集合距离最小的顶点加入T。
6.3.2 Kruskal算法
依次选择权值最小的边,
6.4 最短路径
带权路径长度:顶点v0到顶点v1的一条路径所经过的边的权值之和。
6.4.1 Dijkstra算法求单源最短路径
即求一个顶点到其他顶点的最短路径。
时间复杂度:O(V2)
注意:不适用与边上带负权值的图
6.4.2 Floyd算法求各个顶点之间的最短路径
时间复杂度:O(V3),允许图中有带负权值的边,但不允许带包含负权值的边组成的回路。
直接看图,更新完后再写邻接矩阵。
依次将各个顶点作为中间结点,重新计算各个顶点之间最短路径。
计算完成后的邻接矩阵A2 即任意顶点之间的最短路径。
6.5 有向无环图描述表达式
有向无环图: 若一个有向图中不存在环,则称为有向无环图。简称为DAG图。
利用有向无环图对存在重复子式的表达式的优化,节省存储空间。
方法:
- 把各个操作数不重复地排成一排
- 标出各个运算符顺序
- 按顺序加入运算符
- 自底向上逐层检查,同层运算合并
6.6 关键路径
AOV网: 若用DAG图表示一个工程,其顶点表示活动,用有向边<Vi, Vj>表示活动Vi必须先于活动Vj进行的一种关系。
6.6.1 拓扑排序
- 从AOV网中选择一个没有前驱的顶点输出
- 从网中删除该顶点和所有以它为起点的有向边
6.6.2 关键路径
在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销,称之为AOE网。
源点(开始顶点): AOE网中入度为0的顶点
汇点(结束顶点): AOE网中出度为0的顶点
关键路径:从源点到汇点的所有路径中,具有最大路径长度的路径
关键活动:关键路径上的活动
寻找关键活动(看书p235)
事件最早发生时间ve(k):
从源点开始到vk的最长路径长度
6.7 例题
1.判断有向图中是否存在回路,除可以使用拓扑排序外,还可以利用__
A.求关键路径的方法 B.求最短路径的Dijkstra算法 C.深度优先遍历算法 D.广度优先算法
解答:选C,对顶点v进行深度优先遍历时,出现一条从<u,v>的边则存在回路(把v视为未搜索过)
2.使用DFS算法递归遍历一个无环有向图,并在退出递归时输出相应结点,这样得到的顶点序列是
A.逆拓扑有序 B.拓扑有序 C.无序的 D.都不是
解答:选A
7.查找
7.1 顺序查找
7.2 折半查找
7.3 分块查找
7.4 B树和B+树
7.5 散列表
7.6 例题
1.顺序查找适合于存储结构为__的线性表
A.顺序存储结构或链式存储结构 B.散列存储结构
C.索引存储结构 D.压缩存储结构
解答: 选A。
2.【真题】下列选项中,不能构成折半查找中关键字比较序列的是__
A.500,200,450,180 B.500,450,200,180
C.180,500,200,450 D.180,200,500,450
解答:选A。
- 第一次比较500,下次出现的关键字一定x<=500
- 第二次比较200,下次出现的关键字一定200<=x<=500
- 第三次比较450,下次出现的关键字一定200<=x<=450