数据结构

一. 概述

数据结构是相互之间存在一种或多种特定关系的数据元素集合
主要包括三个方面:逻辑结构、存储结构和数据的运算
算法的设计取决于逻辑结构,而其实现依赖于存储结构
逻辑结构

  • 线性结构:线性表
  • 非线性结构:集合、树、图

存储结构

  • 顺序存储:逻辑上相邻的元素,物理位置上也相邻,存储单位地址连续
  • 链式存储:借助元素存储地址的指针来表示逻辑关系,不要求物理上相邻
  • 索引存储:存储元素信息同时建立索引表,索引表每项称为索引项,方便快速检索,缺点是占用额外空间
  • 散列存储:根据关键字直接计算出存储地址,又称哈希存储

二. 线性表

1. 线性表的定义和基本操作

线性表是具有相同数据类型的n个数据元素的有限序列
除第一个元素外,有且只有一个直接前驱,除最后一个元素外,有且只有一个后继
基本操作:初始化、增删改查、返回表长、回收线性表

2. 线性表的顺序表示

顺序存储的线性表称为顺序表,用一组地址连续的存储空间一次存储线性表中元素
顺序表的特点是逻辑顺序和物理顺序相同,可以根据地址的相对位置直接访问,实现随机存储
插入、删除、查找的时间复杂度均为O(n)
顺序表应用题

3. 线性表的链式表示

3.1. 单链表

线性表的链式存储又称单链表,通过一组任意的存储单元来存储线性表中元素
每个链表节点,除存放元素自身信息,还存放一个指向后继的指针,查找特定节点时,需要从表头开始遍历
为了操作上的方便,一般使用一个头结点,其指针指向线性表第一个元素
(这样既统一了第一个位置和其他位置的操作,又统一了空表和非空表的操作)
头插法:将节点插到最前端,得到链表顺序与插入顺序相反
尾插法:记录尾指针,从最后端插入,并更新尾指针,注意最后尾指针悬空
其他操作有按序号查找、按值查找、插入到指定位置、删除节点、求表长
拓展:前插操作和删除节点操作可以通过交换数据域来灵活实现

3.2 双链表

双链表节点中存在两个指针,分别指向前驱和后继

//p节点后插入s
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
//p节点后删除q
p->next = q->next'
q->next->prior = p;
free(q);
3.3 循环单链表、循环双链表、静态链表

循环单链表判空条件为头结点的指针是否等于头指针,有时不设头指针而仅设尾指针
循环双链表与单链表不同在于其头结点的前驱还要指向尾节点,为空表时,头结点的前驱和后继都为自己
静态链表借助数组来描述链式存储,节点也有数据域和指针域,其指针域存储节点的相对地址(游标),以next==-1作为结束的标志
链表应用题

三. 栈和队列

1. 栈

栈是只允许在一端进行插入或删除的线性表,$ n个不同元素入栈,出栈的排列有\frac{1}{n+1}C^{n}_{2n} $
采用顺序存储的栈称为顺序栈,采用连续存储单元存放自栈底到栈顶的数据
栈空条件:s.top==-1,栈满条件:s.top = MaxSize-1
此时入栈操作先移动指针,再存放数据,出栈操作先出数据,再移动指针

  • 共享栈:top0=-1时0号栈空,top1=MaxSize时1号栈空,top1-top0==1时栈满
  • 链栈:便于共享空间,不存在栈满情况,所有操作在表头进行

2. 队列

队列只允许在队尾一段插入,另一端队头删除

2.1 顺序存储

队列的顺序实现同样是分配一块连续的存储单元,附设两个指针
初始q.front=0、q.rear=0
循环队列:可以看出q.rear==Maxsize时队列并不一定满,可以通过取余操作将数组假定成环状空间
出队:q.front=(q.front+1)%MaxSize
入队: q.rear=(q.rear+1)%MaxSize
判断队空队满有三种方式

  • 牺牲一个单元:(q.rear+1)%MaxSize == q.front队满,q.rear = q.front队空
  • 记录元素个数:q.size=0和q.size=MaxSize
  • 新增tag标志:若因删除操作导致两指针相等,则队空,因插入导致相等,则队满
2.2 链式存储和双端队列

队列的链式存储实际上是一个带有队头指针和队尾指针的单链表
使用头结点进行插入和删除的统一操作,当删除节点为尾结点时,置空,其余操作和单链表一致
双端队列指允许在两端进行入队和出队的队列,除此之外还有
输入受限的的双端队列:另一端只允许删除
输出受限的双端队列:另一端只允许插入

2.3 矩阵压缩

二维数组转一维数组的映射计算
对称矩阵的压缩
三角矩阵的压缩
稀疏矩阵的数组存储和十字链表存储

四. 串

详见串的模式匹配
ababaaababaa的next数组(-1.0.0.1.2.3.1.1.2.3.4.5)
ababaaababaa的nextval数组(0.1.0.1.0.4.2.1.0.1.0.4)

五.树与二叉树

1. 树的定义和性质

树是一种递归的的数据结构,除根节点外有且只有一个前驱,所有节点有多个后继(分支有向),n个节点有n-1条边
节点的孩子个数称为节点的度,树中节点的最大度数称为树的度,度为0的节点称为叶子结点
路径长度是路径上所经过边的个数
树的节点数等于所有节点度数和加一

2. 二叉树的定义和性质

二叉树每个节点至多有两颗子树(不存在度大于2的节点),并且子树有左右之分
满二叉树:高度为h,且含2h-1个节点的二叉树,除叶子节点外每个节点度数为2
假设根节点编号为1,则编号为i的节点做孩子为2i,右孩子为2i+1
完全二叉树:每个节点与相同高度满二叉树对应编号节点相同
二叉排序树:左子树所有节点关键字均小于右子树,左子树和右子树同样是二叉排序树
平衡二叉树:任一节点左右子树深度差不超过一

顺序存储结构:按照满二叉树编号形式将节点映射到数组中进行存储
链式存储结构:节点包含数据域、左指针域、右指针域,含n个节点的二叉链表有n+1个空链域

3. 二叉树的遍历和线索二叉树

3.1 二叉树遍历

分为先序(NLR)、中序(LNR)、后序(LRN)、层次遍历
先序遍历:中左右
中序遍历:左中右
后序遍历:左右中
先序、中序、后序的非递归实现,借助栈见二叉树应用题
层次遍历借助队列,层序序列和中序序列可以唯一确定二叉树
先序序列和后序序列无法确定二叉树

3.2 线索二叉树

传统二叉链表存储只能体现父子关系,不能直接得到节点在遍历中的前驱或后驱
规定:若无左子树,左指针域指向前驱,若无右子树,右指针域指向后继,此时还要添加标志域作为区分
二叉树的线索化是将二叉链表的空指针该位指向前驱或后继的线索,线索化实质是对二叉树进行一次遍历

//中序线索二叉树,要存储上一节点用于更新
void InThread(ThreadTree &p,ThreadTree &pre){
  if(!p) return;//边界条件
  InThread(p->left,pre);//左递归
  //处理
  if(!p->left) p->left = pre,p->ltag = 1;//建立前驱线索
  if(pre&&pre->right==NULL) pre->right = p,pre->rtag =1;//建立前驱的后继线索
  pre = p;//更新前驱
  InThread(p->right,pre)//右递归
}
//遍历线索二叉树时,有后继优先按线索遍历,否则循环找最坐下节点

4. 树、森林

存储结构
双亲表示法:连续空间存储每个点,每个节点增设伪指针指示双亲在数组的位置
孩子表示法:每个节点的孩子都用单链表链成线性结构,类似图的邻接表法
孩子兄弟表示法(二叉树表示):左孩子右兄弟,同样用于树转化二叉树,森林转化成二叉树

并查集

5. 二叉排序树、平衡树和哈夫曼树

5.1 二叉排序树

左子树节点<根节点<右子树节点,中序遍历可以得到递增有序序列
二叉树的查找、插入、构造、删除
查找成功的平均查找长度ASL = 每个节点查找成功地比较次数和/总节点数
查找失败的平均查找长度 = 虚构的节点比较次数和/虚构节点数

5.2 平衡二叉树

任意节点的左、右子树高度差绝对值不超过1
平衡二叉树的插入导致不平衡

  • LL平衡旋转
  • RR平衡旋转
  • LR平衡旋转:左孩子的右子树上插入节点,先左旋再右旋
  • RL平衡旋转:右孩子的左子树上插入节点,先右旋再左旋
    平衡二叉树的最少节点数:1、2、4、7、12、20
5.3 哈夫曼树

数的节点赋予某种特殊意义的值,称为节点的权
树中所有叶节点的带权路径长度和称为树的带权路径长度WPL
带权路径长度最小的数称为哈夫曼树,也称最优二叉树
哈夫曼树的构造
一般应用于哈夫曼编码,使得频率较低的字符赋予较长的编码,起到压缩数据的效果

六. 图

1. 定义

图由点集和边集组成,分为有向图和无向图
简单图不存在重复边、不存在顶点到自身的边
无向图的完全图有n(n-1)/2条边、有向图的完全图有n(n-1)条边
连通图的生成树是包含图全部顶点的极小连通子图
无向图全部顶点度的和等于边数的2倍
有向图的入度等于出度等于边数
顶点不重复出现的路径叫简单路径,除第一个顶点和最后一个顶点,其他顶点不重复出现的回路叫简单回路

2. 图的存储和操作

邻接矩阵:使用二维矩阵表示顶点间连接关系
邻接表:顶点采用顺序存储,每个顶点建立一个链表存储相邻的节点(其实是边)
十字链表:有向图的链式存储结构,容易求的顶点的入度和出度
邻接多重表:无向图的另一种链式存储结构,同一条边只用一个节点表示,执行删除更快捷

3. 图的遍历

需要记录节点是否访问,防止陷入死循环
广度优先使用辅助队列,可以解决非带权图的单源最短路径问题
深度优先使用递归或者栈

4. 最小生成树

Prim算法(树的扩张)
初始时任选一点,然后贪心选择权值最小的邻近边,用于边稠密的图
Kruskal算法(树的合成)
不断选取未被选取过且权值最小的边,用于点较多的图

5. 最短路径

Dijkstra算法

  • 初始化顶点,并记录第一轮路径及其长度
  • 从当前轮路径中选出最短路径,将最短路径目标节点加入集合
  • 更新修改源节点到任一顶点最短路径距离
  • 重复上面过程

Floyd算法

6. 拓扑排序和关键路径

有向无环图的顶点组成的序列,每个AOV网都有一个或多个拓扑排序序列
拓扑排序详解

在带权有向图无环中,用顶点表示时间,有向边表示活动,称为AOE网
完成整个工程的最短时间就是关键路径的长度,关键路径不唯一

  • 从源点出发,按拓扑排序求最早发生时间ve
  • 从汇点出发,逆拓扑排序求最迟发生时间vl
  • 根据ve求所有弧的最早发生时间e
  • 根据vl求所有弧的最晚发生时间l
  • 求所有活动的差额d,差额为0的活动构成关键路径

七. 查找

1. 顺序查找

顺序查找
折半查找:ASL与排序二叉树计算方式相同,查找过程的判定树是平衡树
分块查找:块内无序,块之间有序,先根据索引表查找块,再在块内查找

2. B树和B+树

B树,多路平衡查找树
每个节点至多m棵子树,即至多m-1个关键字
非终端节点的根节点至少两棵子树
非叶子节点至少m/2(向上取整)棵子树
所有叶子节点都在同一层次

B树的高度(磁盘存取次数)
B树的查找
B树的插入:插入后关键字个数大于m-1必须对节点进行分裂,并把中间位置节点往上传
B树的删除:删除后小于m/2(向上取整)-1,则需要合并
分为三种情况--直接删除、兄弟够借、兄弟双亲合并

B+树:n个关键字的节点只含n棵子树,叶节点包含信息,关键字重复出现,每次查找都要从根节点到叶子节点

3. 散列表

散列函数:关键字映射成关键字对应地址的函数
散列表是一种关键字和存储地址的直接映射关系
散列函数的构造方法:直接定址法、除留余数法、数字分析法、平方取中法
处理冲突的方法:开放定址法(不能删除)--包括线性探测、平方探测、再散列、伪随机序列法
拉链法、同义词存储在线性链表中
查找成功与失败性能计算

使用散列函数H(key)=key%11
将{1,13,12,34,38,33,27,22}插入散列表
对于线性探测法:ASL成功 = (1+1+1+3+4+1+2+8)/8
ASL失败 = (9+8+7+6+5+4+3+2+1+1+1)/11
对于拉链法:ASL成功 = (1×4 + 2×3 +3)/8
ASL失败 = (3+4+2+1+1+3+1+1+1+1+1)/11

八. 排序

1. 内部排序

内部排序详解

2. 外部排序

归并排序
多路归并排序和败者树
置换-选择排序
最佳归并树

posted @ 2022-08-11 01:13  失控D大白兔  阅读(202)  评论(0编辑  收藏  举报