第七章 数据结构
- 数据结构:线性结构、数组、矩阵和广义表、树和二叉树、图
1.线性结构
-
线性结构:每个元素最多只有一个出度和入度,表现为一条线状。线性表按存储方式分为顺序表和链表。
-
存储结构:
- 顺序存储:用一组地址连续的存储单元依次存储线性表中的数据元素,使得逻辑上相邻的元素物理上也相邻。
- 链式存储:存储各数据元素的结点的地址并不要求是连续的,数据元素逻辑上相邻,物理上分开。
2.线性表
- 顺序存储和链式存储对比:
性能类别 | 具体项目 | 顺序存储 | 链式存储 |
空间性能 | 存储密度 | =1,更优 | <1 |
容量分配 | 事先确定 | 动态改变,更优 | |
时间性能 | 查找运算 | O(n/2) | O(n/2) |
读运算 | O(1),更优 | O([n+1]/2),最好情况为1,最坏情况为n | |
插入运算 | O(n/2),最好情况为0,最坏情况为n | O(1),更优 | |
删除运算 | O([n-1]/2) | O(1),更优 |
对于顺序存储和链式存储,进行查找的时间复杂度显然就是O(n/2)。
读运算:顺序存储中,无论元素在哪个位置,直接访问数组下标即可,所以是O(1);链式存储中,如果读取第一个,则为最好情况1,如果读取最后一个,则为最坏情况n,因为要依次访问这些元素的指针域,所以总的为O((n+1)/2)。
插入运算:顺序存储中,假设元素依次为2、5、6、1、9(n=5),那么最好情况是在最后一个元素后面插入一个新元素,此时不需要移动任何元素,为0,最坏情况是在第一个元素前面插入一个新元素,此时需要将2、5、6、1、9全部向后移动一个位置,为5,所以平均下来需要移动(0+5)/2个元素,即为O(0+5)/2=O(n/2);链式存储中,只需要移动插入元素位置的前驱指针和后继指针就可以了,所以为O(1)。
删除运算:顺序存储中,假设元素依次为2、5、6、1、9(n=5),那么最好情况是将最后一个元素删除,此时不需要移动任何元素,为0,最坏情况是将第一个元素删除,此时需要将5、6、1、9全部向前移动一个位置,为4,所以平均下来需要移动(0+4)/2个元素,即为O(0+4)/2=O((n-1)/2);链式存储中,只需要移动删除元素位置的前驱指针和后继指针就可以了,所以为O(1)。
3.栈和队列
- 队列、栈也是线性结构,结构如下图,队列是先进先出,分队头和队尾;
- 栈是先进后出,只有栈顶能进出。
- 循环队列中,头指针指向第一个元素,尾指针指向最后一个元素的下一个位置,因此,当队列空时,head=tail,当队列满时,head=tail,这样就无法区分了,因此,一般将队列少存一个元素,这样,队列满时的条件就变为tail+1=head,而考虑是循环队列,必须除以最大元素数来取余数,即(tail+1)%size=head,如上图右边两个公式。循环队列的长度公式为(Q.tail–Q.head)%size。
例题:
我们再来看上面这道例题,左端可进可出,右端只能进不能出。四个元素依次入队,得不到哪种出队序列???
A选项:e1、e2、e3、e4依次从左端入队,就得到了e4、e3、e2、e1这样的队列,此时依次出队即可得到A选项的结果。
B选项:e1、e2先从左端入队,之后e3从右端入队,最后e4从左端入队,就能得到e4、e2、e1、e3的队列,满足B选项。
C选项:e1、e2先从右端入队,之后e3、e4再从左端入队,就可以得到e4、e3、e1、e2的队列,即符合C选项。
D选项:仔细分析这个队列的入队出队规则,我们发现e1和e2无论如何都是相邻的元素,所以无法得到D选项这样的队列。
4.串
- 字符串是一种特殊的线性表,其数据元素都为字符。
- 空串:长度为0的字符串,没有任何字符。
- 空格串:由一个或多个空格组成的串,空格是空白字符,占一个字符长度。
- 子串:串中任意长度的连续字符构成的序列称为子串。含有子串的串称为主串,空串是任意串的子串。
- 串的模式匹配算法:子串的定位操作,用于查找子串在空串中第一次出现的位置的算法。
- 基本的模式匹配算法:也称为布鲁特–福斯算法,基本思想就是从主串的第1个字符起与模式串的第1个字符比较,若相等,则继续逐个字符进行后续的比较;否则从主串中的第2个字符起与模式串的第1个字符重新比较,直至模式串中每个字符依次和主串中的一个连续的字符序列相等时为止,此时称为匹配成功。
- KMP算法:对基本模式匹配算法的改进,每当匹配过程中出现相比较的字符不相等时,不需要回溯主串的字符位置指针,而是利用已经得到的“部分匹配”结果将模式串向右“滑动”尽可能远的距离,再继续进行比较。
5.数组
- 数组是定长线性表在维度上的扩展,即线性表中的元素又是一个线性表。
- 数组结构的特点:数据元素数目固定,数据元素类型相同,数据元素的下标关系具有上下界的约束且下标有序。
- 数组数据元素固定,一般不做插入和删除运算,适用于采用顺序结构
对于一维数组来说,a[i]的存储地址计算公式为:a + i×len,a代表起始位置,i代表数组下标,len代表每个元素所占字节数。
假设数组a中每个元素占两个字节,那么对于起始位置为0、元素a[1]来说,它的存储地址为:0 + 1×2=2,因为0和1两个位置被数组的第一个元素a[0]占用。
对于上面这道例题,二维数组中,根据计算公式,元素a[2][3]按行优先存储的存储地址为:a + (2×5+3)×2=a + 26。
6.矩阵
- 特殊矩阵:矩阵中的元素(或非0元素)的分布有一定的规律。常见的特殊矩阵有对称矩阵、三角矩阵(上三角矩阵和下三角矩阵)和对角矩阵。
- 稀疏矩阵:在一个矩阵中,若非零元素的个数远远少于零元素个数,且非零元素的分布没有规律。
- 存储方式为三元组结构,即存储每个非零元素的(行,列,值)
7.广义表
- 就是线性表的推广,是由0个或多个单元素或字表组成的有限序列。
- 广义表和线性表的区别:线性表的元素都是结构上不可分的单元素,而广义表的元素既可以单元素,也可以是有结构的表。
- 广义表一般记为: L S = ( a 1 , a 2 . . . a n ) LS=(a_1,a_2...a_n) LS=(a1,a2...an)
例1:广义表LS1的长度为元素的个数(单个元素算一个元素,子表这个整体算一个元素),所以LS1中,a是一个元素,(b,c)是一个元素,(d,e)是一个元素,即长度为3。深度为所含括号的层数,LS1中最外层有一层括号,内部的(b,c)和(d,e)算同样的一层,所以总层数为2,即深度为2。
例2:要获取LS1中的字母b,则需要的操作为:先取表尾,得到((b,c),(d,e));再取表头,得到(b,c);再取表头即可得到字母b。即操作序列为:Head(Head(Tail(LS1)))。
8.树
- 树结构是一种非线性结构,树中的每一个数据元素可以有两个或两个以上的直接后继元素,用来描述层次结构关系。
没有入度的结点称为根节点。
9.二叉树
- 二叉树是n个节点的有限集合,它或者是空树,或者是由一个根节点及两颗互不相交的且分别称为左、右子树的二叉树所组成。与树的区别在于每个根节点最多只有两个孩子结点。
- 二叉树的重要特性如下:
- 二叉树第i层(i≥1)上最多有 2 i − 1 2^{i-1} 2i−1个结点。
- 高度为k的二叉树最多有 2 k − 1 2^k-1 2k−1个结点(k≥1)
- 对于任何一棵二叉树,若其终端结点数为 n 0 n_0 n0,度为2的结点数为 n 2 n_2 n2,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
- 具有n个结点的完全二叉树的深度为 [ l o g 2 n ] + 1 [log_2^n]+1 [log2n]+1。
- 满二叉树:每层都是满结点的。
- 完全二叉树:完全二叉树的k–1层是满结点的,第k层结点从左到右是满的,但是左边缺一个即为非完全二叉树。
10.二叉树的存储结构
- 二叉树的顺序存储结构:
- 就是用一组连续的存储单元存储二叉树中的节点,按照从上到下,从左到右的顺序依次存储每个节点。
- 对于深度为k的完全二叉树,除第k层外,其余每层中节点数都是上一层的两倍。假设有编号为i的节点,则有:
- 若i=1,孩结点为根节点,无双亲。
- 若i>1,该结点的双亲为(i+1)/2(取整数)。
- 若 2 i ≤ n 2i≤n 2i≤n,则该结点的左孩子编号为2i,否则无左孩子。
- 若 2 i + 1 ≤ n 2i+1≤n 2i+1≤n,则该结点的右孩子编号为2i+1,否则无右孩子。
- 若 i i i为奇数且不为1,则该结点左兄弟的编号为 i − 1 i-1 i−1,否则无左兄弟。
- 若 i i i为偶数且小于n,则该结点右兄弟的编号为 i + 1 i+1 i+1,否则无右兄弟。
- 二叉树的链式存储结构:
- 一般用二叉树链表来存储二叉树节点,二叉链表中除了该节点本身的数据外,还存储有左孩子结点的指针、右孩子结点的指针,即一个数据+两个指针。
- 每个二叉链表节点存储一个二叉树节点,头指针则指向根节点。
11.二叉树的遍历
- 一颗非空的二叉树由根节点、左子树、右子树三部分组成。
对于上面的二叉树,我们来求一下它的先序、中序、后序以及层次遍历:👇👇👇
①先序遍历:采用DLR(根左右),得到的结果是:1 24578 36。
②中序遍历:采用LDR(左根右),得到的结果是:42785 1 36。
③后序遍历:采用LRD(左右根),得到的结果是:48752 63 1。
④层次遍历:依次获取每层的结点,得到的结果是:1 23 456 7 8。
要想唯一确定一棵二叉树,必须要有中序遍历的序列结果!!!
12.线索二叉树
-
引入线索二叉树是为了保存二叉树遍历时某节点的前驱节点和后继节点的信息
-
**若n个结点的二叉树采用二叉链表做存储结构,则链表中必然有 n+1 个空指针域。**利用这些空指针域来存放节点的前驱和后继节点信息,为此,需要增加两个标志,以区分指针域存放的到底是孩子结点还是遍历节点,如下:
若二叉树的二叉链表采用上述结构,则称为线索链表,其中指向前驱、后继节点的指针称为线索,加上线索的二叉树称为线索二叉树。
13.最优二叉树(哈夫曼树)
- 最优二叉树又称为哈夫曼树,是一类带权路径长度最短的树,相关概念如下:
- 路径:树中一个结点到另一个结点之间的通路。
- 结点的路径长度:路径上的分支数目。
- 树的路径长度:根节点到达每一个叶子节点之间的路径长度之和。
- 权:节点代表的值。
- 结点的带权路径长度:该结点到根节点之间的路径长度乘以该节点的权值。
- 树的带权路径长度(树的代价):树的所有叶子节点的带权路径长度之和。
14.树和森林
-
树的存储结构:
- 双亲表示法
- 孩子表示法
- 孩子兄弟表示法
-
树和森林的遍历
- 由于树中每个节点可能有多个子树,因此遍历树的方法有两种:-
- 先根遍历:先访问根节点,再依次遍历根的各颗子树。
- 后根遍历:先遍历根的各颗子树,再访问根节点。
- 由于树中每个节点可能有多个子树,因此遍历树的方法有两种:-
-
树和二叉树的转换:
- 规则是:树的最左边节点作为二叉树的左子树,树的其他兄弟节点作为二叉树的右子树节点。
- 采用连线法,将最左边节点和其兄弟节点都连接起来,而原来的父节点和兄弟节点的连线则断开。
15.查找二叉树
- 二叉树每个节点的所有左孩子结点值都小于父节点值,而所有右孩子结点值都大于父节点值,这种数据结构可以方便查找、插入等数据操作。
- 二叉排序树的查找效率取决于二叉排序树的深度。对于结点个数相同的二叉排序树,平衡二叉树的深度最小,而单枝树的深度是最大的,故效率最差。
16.平衡二叉树
- 平衡二叉树就是任意左右子树层次相差不超过1。
17.图
- 图也是一种非线性结构,图中任意两个节点间都可能有直接关系。相关定义如下:
- 无向图:图的结点之间连接线是没有箭头的,不分方向。
- 有向图:图的结点之间连接线是箭头,区分A到B,和B到A是两条线。
- 完全图:无向完全图中,节点两两之间都有连线,n个结点的连接数为 ( n − 1 ) + ( n − 2 ) + . . . + 1 = n ∗ ( n − 1 ) / 2 (n-1)+(n-2)+...+1=n*(n-1)/2 (n−1)+(n−2)+...+1=n∗(n−1)/2;有向完全图中,节点两两之间都有互通的两个箭头,n个节点的连接数为 n ∗ ( n − 1 ) n*(n-1) n∗(n−1)
- 度、出度和入度:顶点的度是关联与该顶点的边的数目。在有向图中,顶点的度为出度和入度之和。出度是以该顶点为起点的有向边的数目。入度是以该顶点为终点的有向边的数目。
- 路径:存在一条通路,可以从一个顶点到达另一个顶点,有向图的路径也是有方向的。
- 连通图和连通分量:针对无向图,若从顶点v到顶点u之间是有路径的,则说明v和u之间是连通的,若无向图中任意两个顶点之间都是连通的,则称连通图。无向图G的极大连通子图称为其连通分量。
- 强连通图和强连通分量:针对有向图。若有向图任意两个顶点间都互相存在路径,即存在v到u,也存在u到v的路径,则称为强连通图。有向图中的极大连通子图称为其强连通分量。
- 网:边带权值的图称为网。
18.图的存储
-
邻接矩阵
-
邻接链表
19.图的遍历
20.图的最小生成树
-
假设有n个节点,那么这个图的最小生成树有n–1条边,这n–1条边会将所有顶点都连接成一个树,并且这些边的权值之和最小,因此称为最小生成树。
-
普里姆算法(以顶点为中心,适合稠密图)
-
克鲁斯卡尔算法(以边为中心,适合稀疏图)
21.图的拓扑序列
- AOV网(以顶点表示活动的网):在有向图中,以顶点表示活动,用有向边表示活动之间的优先关系。
- AOV网用来表示大的工程项目执行计划,因此不能出现有向环。