第一章.数据结构基本概念
1.1什么是数据结构
数据:所有能输入到计算机中并被计算机程序处理的符号的总称。
数据元素:数据的基本单位,一个数据元素可由若干个数据项组成,数据项是数据元素不可分割的最小单位,如学生记录包含学号、姓名、年龄。
数据对象:具有相同性质数据元素的集合,是数据的子集。
数据类型:一个值的集合和定义在此集合上一组操作的总称,按值是否可再分,可分为原子类型(基本类型)与结构类型(构造类型),而抽象数据类型是特殊的数据类型。
- 原子类型:值不可再分,如整型、字符型。
- 结构类型:值可再分,如数组的值由若干分量组成。
- 抽象数据类型(ADT):一个数据模型和一组操作,抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关,即无论其内部结构如何变化,只要数学特性不变,都不影响外部使用。
(数据类型不涉及数据元素之间的相互关系)
数据结构:相互之间存在一种或多种特定关系的数据元素的集合,这种关系称为逻辑结构,包括逻辑结构、存储结构和数据的运算三个方面,一个算法的设计取决于逻辑结构,而算法的实现取决于存储结构。
1.2数据结构的定义及三要素
1.2.1数据结构的定义
定义形式是一个二元组:
D
a
t
a
S
t
r
u
c
t
u
r
e
=
<
D
,
S
>
Data Structure=<D,S>
DataStructure=<D,S>,D是数据元素的有限集,S是D上关系的有限集合,例:
复试数据结构的定义:
C
o
m
p
l
e
x
=
(
C
,
R
)
Complex=(C,R)
Complex=(C,R),C={c1,c2}是实数集合,R={P}是定义在C上的一种关系{<c1,c2>},<c1,c2>表示c1是复数的实部,c2是复数的虚部,如:
1.2.2数据结构的三要素
数据结构包含逻辑结构、存储结构和数据的运算三个方面。
1.逻辑结构
(广义表也是线性表的推广)
- 逻辑结构是对数据元素之间逻辑关系的描述,从逻辑关系上描述数据,与数据的存储无关,独立于计算机。
2.物理结构
存储结构是数据结构在计算机中的表示,也称物理结构,包括数据元素的表示和关系的表示,存储结构是用计算机语言实现的逻辑结构。
- 存储结构是对数据元素在计算机中的存储及其逻辑关系的表现,也称物理结构。
- 是逻辑结构用计算机语言的实现,依赖于计算机语言数据。
3.数据的操作
1.2.3抽象数据类型
按照值的不同特性三种抽象数据类型(后两种统称结构类型):
- 原子类型:变量值部分再分。
- 固定聚合类型:属该类型的变量,其值由确定数目的成分按某种结构组成,如复数是由两个实数依确定的次序关系构成。
- 可变聚合类型:值的成分的数目不确定,如“有序整数序列”的抽象数据类型,其长度可变。
1-6中,值的成分不确定的数据类型,e1、e2、e3可以是整数、字符、字符串等等,故称为多形数据类型。
1.3算法
1.3.1概念
定义:对特定问题求解步骤的一种描述,是指令的有序序列,每条指令表示一个或多个操作,有以下特性:
- 有穷性:一个算法应在有穷步骤后完成,且每一步在有穷时间内完成。
- 确定性:每条指令有确切的含义,不存在二义性,且只有一个入口和出口。
- 可行性:可执行,即操作都可通过已实现的基本运算执行有限次来实现。
- 输入:一个算法有零个或多个输入。
- 输出:一个算法有一个或多个输出。
1.3.2时间复杂度
通常采用算法中基本运算的频度
f
(
n
)
f(n)
f(n)来分析算法的时间复杂度。算法的时间复杂度不仅依赖于问题规模n,也取决于待输入数据的性质(如数据的初始状态)。
1.3.3空间复杂度
第二章.线性表
1.基本概念
2.线性表的顺序表示
2.1基本概念
线性表的顺序存储称为顺序表,用一组地址连续的存储单元依次存储线性表中的数据元素,逻辑上相邻的两个元素物理位置上也相邻。由于每个数据元素的存储位置都和线性表的起始位置相差一个和该数据元素的位序成正比的常数,因此线性表中任意数据元素都可随机存取。
顺序表存储空间的分配
a.静态分配:数组的大小和空间事先确定,一旦空间占满,再加入新元素会溢出,静态定义语句:
ElemType data[MaxSize];
b.动态分配:存储数组的空间在程序执行过程中通过动态分配语句分配,占满后可另开辟更大存储空间,并移动元素,动态定义语句:
ElemType* data;
data=(ElemType*)malloc(sizeof(ElemType)*MaxSize));
动态分配并不是链式分配,同样属于顺序存储结构,物理结构不变,仍是随机存取。
顺序表的特点
- 随机访问,即通过首地址和元素序号可在O(1)时间内找到指定的元素。
- 存储密度高,每个结点只存储数据元素。
- 逻辑上相邻物理上也相邻,所以插入和删除操作需要移动大量元素。
2.2基本操作
插入操作:在第i个位置( 1 < = i < = L . l e n g t h + 1 1<=i<=L.length+1 1<=i<=L.length+1)插入新元素。
- 最好情况:最后一个位置插入,不移动元素。
- 最坏情况:第一个位置插入,n个元素向后移动。
- 平均情况:有(n+1)个插入位置,第i个位置上需移动(n-i+1)个元素,故:
删除操作:删除第i个位置上的元素(
1
<
=
i
<
=
L
.
l
e
n
g
t
h
1<=i<=L.length
1<=i<=L.length)。
- 最好情况:删除最后一个元素,不移动元素。
- 最坏情况:删除第一个元素,后(n-1)个元素向前移动。
- 平均情况:有i个删除位置,第i个位置删除时需移动(n-i)个元素,故:
按值查找:查找第一个等于e的元素。
- 最好情况:元素在表头,只比较一次。
- 最坏情况:元素在表尾,比较n次。
- 平均情况:元素在位序i处,比较i次,故:
2.线性表的链式表示
链式存储线性表时,不需要使用地址连续的存储单元,不要求逻辑上相邻物理上也相邻,通过“链”建立起数据元素之间的逻辑关系,因此插入和删除操作不需要移动元素,只需要修改指针,但也会失去顺序表可随机存取的优点。
2.3.1单链表
每个链表结点除存放元素自身信息外,还需存放一个指向其后继的指针:
通常用头指针来标识一个单链表,用头结点来是带头结点的链表中第一个结点,结点内不存储信息,且头结点可简化操作。
注意,不管带不带头结点,头指针都始终指向链表的第一个结点,而头结点是带头结点的链表中的第一个结点,结点中通常不存储信息。
建立单链表:
2.3.2双链表
链表中每个结点设立指向前驱的指针域prior、指向后继的指针域next,使得可以快速找到指定结点的前驱,当要删除指定结点、在指定结点前插入结点时,时间复杂度为O(1)。
2.3.3循环单链表
循环单链表与单链表的区别在于,表中最后一个结点的指针不是NULL,而是指向头结点,从而整个链表形成一个环。在单链表中只能从表头结点开始往后顺序遍历整个链表,而循环单链表可以从表中任意一个结点开始遍历整个链表。
- 判断是否是空链表:head->next==head
- 判断是否是表尾结点:p->next==head
循环单链表的插入、删除算法与单链表几乎一样,所不同的是如果操作是在表尾进行,则执行的操作不同,需要使其继续保持循环的性质。当然,正是英文循环单链表是一个环,因此,在任何一个位置上的插入和删除操作都是等价的,无需判断是否是表尾。
有时对单链表常做的操作是在表头和表尾进行的,可对循环单链表不设头指针而仅设尾指针,此时对表头和表尾进行操作都只需要O(1)的时间复杂度。
2.3.4循环双链表
循环双链表是指构成链表的每个结点中设置两个指针域:一个指向其直接前驱的指针域prior,一个指向其直接后继的指针域next,且头结点与尾结点链接。
在循环双链表中,某结点*p为尾结点时,p->next==L;当循环双链表为空时,其头结点prior和next域均为L。
2.3.5静态链表
typedef struct{
ElemType data;
int next;
}SLinkList[MaxSize];//静态分配方式,不能动态增加
静态链表虽用数组进行存储,但其插入、删除操作与动态链表相同,只需要修改指针,而不需要移动元素。
2.4顺序表和链表的比较
第三章.栈、队列
1.栈
1.1基本概念
栈是只允许在一端进行插入或删除操作的线性表(操作受限的线性表),在计算机的实现方式有多种:
栈的操作特性为后进先出,有如下定义:
- 栈顶:线性表允许进行插入、删除的那一端。
- 栈底:线性表不允许插入、删除的那一端。
- 空栈:不包含任何元素的空表。
数学性质:n个不同元素入栈,出栈元素不同排列的个数为 1 / ( n + 1 ) C 2 n n 1/(n+1)C^{n}_{2n} 1/(n+1)C2nn。
1.2顺序栈:栈的顺序表示
使用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时设置指针top指示当前栈顶元素的位置。
共享栈
栈的上溢与下溢
在进行堆栈操作时,要注意判断堆栈是否上溢或下溢。
- 上溢:当栈满时做进栈运算必定产生空间溢出,简称“上溢”,上溢是一种出错状态,应设法避免。
- 下溢:当栈空时做退栈运算也将产生溢出,简称“下溢”,下溢则可能是正常现象,因为栈在使用时,其初态或终态都是空栈,所以下溢常用来作为控制转移的条件。
1.3链栈:栈的链式表示
链栈实际是运算受限的单链表,其插入和删除操作只能在表头位置上进行,因此链栈没有必要像单链表那样附加头结点,栈顶指针top就是链表的头指针。
2.队列
2.1基本概念
队列也是一种操作首先的线性表,只允许在表的一端进行插入,而在另一端进行删除,其操作特性是先进先出。定义如下:
- 队头:允许删除的一端,又称队首。
- 队尾:允许插入的一端。
- 空队列:不含任何元素的空表。
2.2顺序队列与循环队列:队列的顺序存储
顺序队列
循环队列
2.3链式队列:队列的链式存储
2.4双端队列
双端队列是指允许两端都可以进行入队和出队操作的队列,其元素的逻辑结构仍然是线性结构。
- 输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许插入的双端队列称为输出受限的双端队列。
- 输入受限的双端队列:允许在一端进行插入和删除,但在另一端只允许删除的双端队列称为输入受限的双端队列。
3.栈和队列的应用
3.1栈在递归中的应用
如果一个函数、过程或数据结构的定义中又应用了它自身,则这个函数、过程或数据结构称为是递归定义的。递归一般将复杂问题转化为与原问题相似的规模较小的问题求解,大大减少程序代码量,但效率一般不高,实际上有效的递归调用函数应包括两部分:
- 递推方法
- 终止条件
为保证递归调用正确执行,系统设立一个“递归工作栈”,作为整个递归调用过程期间使用的数据存储区,每一层递归包含的信息如:参数、局部变量、上一层的返回地址构成一个“工作记录”,每进入一层递归,就会产生一个新的工作记录压入栈顶,每退出一层递归,就从栈顶弹出一个工作记录。
第四章.串
4.1基本概念
串是零个或多个字符组成的有限序列。
- 空串:长度为零的串称为空串,不包含任何字符。
- 空格串(空白串):构成串的所有字符都是空格的串称为空格串。
- 子串:串中任意个连续字符组成的子序列称为该串的子串,包含子串的串相应地称为主串。
- 子串的序号:子串在主串中首次出现时子串的首字符对应在主串中的序号,空串是任意串的子串,任意串是自身的子串。
- 串相等:如果两个串的串值相等,则称这两个串相等。
在程序中使用的串可分为两种:串变量和串常量。
- 串变量:和整常数、实常数一样,在程序中只能被引用但不能改变其值,即只能读不能写。
- 串变量:和其他类型的变量一样,其值可改变。
4.2存储结构
串的定长顺序存储表示
堆分配的特点是,仍然以一组地址连续的存储空间来存储字符串值,但其所需的存储空间实在程序执行过程中动态分配,故是动态的,变长的。
结点构成:
- data域:存放字符,data域可存放的字符个数称为结点的大小。
- next域:存放指向下一结点的指针。
块链式存储的特点:
- 在块链存储结构下,结点的分配总是以完整的结点为单位,因此为使一个串能存放在整数个结点中,在串的末尾天上不属于串值的特殊字符,以表示串的终结。
- 当一个块(结点)内存放多个字符时,往往会使操作过程变得较为复杂,如在串中插入或删除字符操作时,需要块间移动字符。
4.3模式匹配
子串的定位操作通常称为串的模式匹配,求的是子串(模式串)在主串中的位置。
第五章.数组和广义表
5.1基本概念
数组,是由
n
(
n
≥
1
)
n(n≥1)
n(n≥1)个相同类型的数据元素构成的有限序列,每个数据元素称为一个数组元素,每个元素在
n
n
n个线性关系中的序号称为该元素的下标,下标的取值称为数组的维界,下标的组成个数称为维数。
数组和线性表的关系:数组时线性表的推广,一维数组可视为一个线性表,二维数组可视为其元素也是定长线性表的线性表。
5.2特殊矩阵的压缩存储
压缩存储:为多个值相同的元素只分配一个存储空间,对零元素不分配存储空间,目的是为节省存储空间。
特殊矩阵:指具有许多相同矩阵元素或零元素,且这些相同矩阵元素或零元素分布有一定规律性的矩阵。
特殊矩阵的压缩存储:找出特殊矩阵中值相同矩阵元素的分布规律,将呈现规律性分布的、值相同的多个矩阵元素存储到一个存储空间中。
注意:描述矩阵元素时,行、列号通常从
1
1
1开始,而描述数组时的行、列行从
0
0
0开始。
5.2.1对称矩阵
若对于一个
n
n
n阶方阵
A
[
1...
n
]
[
1...
n
]
A[1...n][1...n]
A[1...n][1...n]中的任意一个元素
a
i
,
j
=
a
j
,
i
(
1
≤
i
,
j
≤
n
)
a_{i,j}=a_{j,i}(1≤i,j≤n)
ai,j=aj,i(1≤i,j≤n),则称其为对称矩阵,对于一个
n
n
n阶方阵,其中的元素可划分为
3
3
3个部分:上三角区、主对角线和下三角区。
存储思路:利用下标的映射关系只存储上三角 ( 1 ≤ i < j ≤ n ) (1≤i<j≤n) (1≤i<j≤n)和主对角线元素 ( i = j ) (i=j) (i=j),若是 n n n阶方阵,则数组的大小为 n ( n + 1 ) / 2 n(n+1)/2 n(n+1)/2。
存储下三角区域+行优先:
当
i
≥
j
i≥j
i≥j时,矩阵
a
i
,
j
a_{i,j}
ai,j的
1
1
1到
(
i
−
1
)
(i-1)
(i−1)行一共有
i
(
i
−
1
)
/
2
i(i-1)/2
i(i−1)/2个元素,第
i
i
i行中,
a
i
,
j
a_{i,j}
ai,j是第
j
j
j个元素,又因数组下标从
0
0
0开始,故有:
下标
k
=
i
(
i
−
1
)
/
2
+
(
j
−
1
)
,
(
i
≥
j
)
k=i(i-1)/2+(j-1),(i≥j)
k=i(i−1)/2+(j−1),(i≥j)
故元素下标之间的对应关系为:
存储上三角区域+行优先:
当
j
≥
i
j≥i
j≥i时,矩阵
a
i
,
j
a_{i,j}
ai,j的
1
1
1到
(
i
−
1
)
(i-1)
(i−1)行一共有
(
i
−
1
)
(
2
n
−
i
+
2
)
/
2
(i-1)(2n-i+2)/2
(i−1)(2n−i+2)/2个元素,第
i
i
i行中,
a
i
,
j
a_{i,j}
ai,j是第
(
j
−
i
+
1
)
(j-i+1)
(j−i+1)个元素(从对角线开始数),又因数组下标从
0
0
0开始,故有:
下标
k
=
(
i
−
1
)
(
2
n
−
i
+
2
)
/
2
+
(
j
−
i
)
,
(
j
≥
i
)
k=(i-1)(2n-i+2)/2+(j-i),(j≥i)
k=(i−1)(2n−i+2)/2+(j−i),(j≥i)
故元素下标之间的对应关系为:
k
=
{
(
j
−
1
)
(
2
n
−
j
+
2
)
+
(
i
−
j
)
,
j
>
i
(
i
−
1
)
(
2
n
−
i
+
2
)
/
2
+
(
j
−
i
)
,
j
≤
i
k=\left\{\right.^{(i-1)(2n-i+2)/2+(j-i),j≤i}_{(j-1)(2n-j+2)+(i-j),j>i}
k={(j−1)(2n−j+2)+(i−j),j>i(i−1)(2n−i+2)/2+(j−i),j≤i
存储下三角区域+列优先:
当
i
≥
j
i≥j
i≥j时,矩阵
a
i
,
j
a_{i,j}
ai,j的
1
1
1到
(
j
−
1
)
(j-1)
(j−1)列一共有
(
j
−
1
)
(
2
n
−
j
+
2
)
/
2
(j-1)(2n-j+2)/2
(j−1)(2n−j+2)/2个元素,第
j
j
j列中,
a
i
,
j
a_{i,j}
ai,j是第
(
i
−
j
+
1
)
(i-j+1)
(i−j+1)个元素(从对角线开始数),又因数组下标从
0
0
0开始,故有:
下标
k
=
(
j
−
1
)
(
2
n
−
j
+
2
)
/
2
+
(
i
−
j
)
,
(
i
≥
j
)
k=(j-1)(2n-j+2)/2+(i-j),(i≥j)
k=(j−1)(2n−j+2)/2+(i−j),(i≥j)
故元素下标之间的对应关系为:
k
=
{
(
i
−
1
)
(
2
n
−
i
+
2
)
+
(
j
−
i
)
,
j
>
i
(
j
−
1
)
(
2
n
−
j
+
2
)
/
2
+
(
i
−
j
)
,
i
≥
j
k=\left\{\right.^{(j-1)(2n-j+2)/2+(i-j),i≥j}_{(i-1)(2n-i+2)+(j-i),j>i}
k={(i−1)(2n−i+2)+(j−i),j>i(j−1)(2n−j+2)/2+(i−j),i≥j
存储上三角区域+列优先:
当
j
≥
i
j≥i
j≥i时,矩阵
a
i
,
j
a_{i,j}
ai,j的
1
1
1到
(
j
−
1
)
(j-1)
(j−1)列一共有
j
(
j
−
1
)
/
2
j(j-1)/2
j(j−1)/2个元素,第
j
j
j列中,
a
i
,
j
a_{i,j}
ai,j是第
i
i
i个元素,又因数组下标从
0
0
0开始,故有:
下标
k
=
j
(
j
−
1
)
/
2
+
(
i
−
1
)
,
(
j
≥
i
)
k=j(j-1)/2+(i-1),(j≥i)
k=j(j−1)/2+(i−1),(j≥i)
故元素下标之间的对应关系为:
k
=
{
i
(
i
−
1
)
+
(
j
−
1
)
,
i
>
j
j
(
j
−
1
)
/
2
+
(
i
−
1
)
,
j
≥
i
k=\left\{\right.^{j(j-1)/2+(i-1),j≥i}_{i(i-1)+(j-1),i>j}
k={i(i−1)+(j−1),i>jj(j−1)/2+(i−1),j≥i
5.2.2三角矩阵
三角矩阵包含下三角矩阵和上三角矩阵两种,下三角矩阵中上三角区所有元素都是同一常量,上三角矩阵中下三角区所有元素都是同一常量。
存储思想:存储思想与对称矩阵类似,不同之处在于存储完下/上三角区和主对角线上的元素之后,紧接着存储对角线上方的常量一次,故,
n
n
n阶三角矩阵可压缩存储在数组
B
[
n
(
n
+
1
)
/
2
+
1
]
B[n(n+1)/2+1]
B[n(n+1)/2+1]中。
由于存储思想与对称矩阵类似,故此处不再作过多推导,只给出公式(均是行优先):
下三角矩阵压缩存储:
上三角矩阵压缩存储:
5.2.3三对角矩阵
三对角矩阵,亦称带状矩阵,对于
n
n
n阶三对角矩阵
A
A
A中的任意元素
a
i
,
j
a_{i,j}
ai,j,当
∣
i
−
j
∣
>
1
|i-j|>1
∣i−j∣>1时,有:
a
i
,
j
=
0
,
(
1
≤
i
,
j
≤
n
)
a_{i,j}=0,(1≤i,j≤n)
ai,j=0,(1≤i,j≤n)
对于三对角矩阵,可利用上述特性,只存储主对角线及其上、下两侧次对角线的元素,其他零元素不进行存储,此时,一个
n
n
n阶的三对角矩阵总共需要
3
n
−
2
3n-2
3n−2的存储空间。
行优先存储
可将
3
3
3条对角线上的元素;
a
i
,
j
=
0
,
(
1
≤
i
,
j
≤
n
,
∣
i
−
j
∣
≤
1
)
a_{i,j}=0,(1≤i,j≤n,|i-j|≤1)
ai,j=0,(1≤i,j≤n,∣i−j∣≤1)按行优先的方式存储在一维数组
B
B
B中,且
a
1
,
1
a_{1,1}
a1,1存储在
B
[
0
]
B[0]
B[0]中。
当
a
i
,
j
a_{i,j}
ai,j处于第
2
2
2到
(
n
−
1
)
(n-1)
(n−1)行之间时,由于从第二行开始的元素个数均为
3
3
3个,故此时前
(
i
−
1
)
(i-1)
(i−1)行的元素个数为
3
(
i
−
2
)
+
2
3(i-2)+2
3(i−2)+2,这是因为第
1
1
1行元素的个数为
2
2
2个,而
a
i
,
j
a_{i,j}
ai,j属于第
i
i
i行第
(
j
−
i
+
2
)
(j-i+2)
(j−i+2)个非零元素,又因数组的下标从
0
0
0开始,故而数组下标
k
k
k与行标
i
i
i、列标
j
j
j的关系为:
k
=
2
i
+
j
−
3
,
2
≤
i
≤
n
,
1
≤
j
≤
n
,
∣
i
−
j
∣
≤
1
k=2i+j-3,2≤i≤n,1≤j≤n,|i-j|≤1
k=2i+j−3,2≤i≤n,1≤j≤n,∣i−j∣≤1
,经验证,当
i
=
1
i=1
i=1时该式依旧成立,故可得关系式:
k
=
2
i
+
j
−
3
,
1
≤
i
,
j
≤
n
,
∣
i
−
j
∣
≤
1
k=2i+j-3,1≤i,j≤n,|i-j|≤1
k=2i+j−3,1≤i,j≤n,∣i−j∣≤1
若已知元素
a
i
,
j
a_{i,j}
ai,j在数组中存储的下标为
k
k
k,则可得
i
=
(
k
−
j
+
3
)
/
2
i=(k-j+3)/2
i=(k−j+3)/2,此时有如下三种情况:
①
①
①当
a
i
,
j
a_{i,j}
ai,j位于主对角线时,就有:
i
=
j
i=j
i=j,则此时
i
=
j
=
(
k
+
3
)
/
3
i=j=(k+3)/3
i=j=(k+3)/3。
②
②
②当
a
i
,
j
a_{i,j}
ai,j位于下侧次对角线时,就有:
i
=
j
+
1
i=j+1
i=j+1,则此时
i
=
j
+
1
=
(
k
+
4
)
/
3
i=j+1=(k+4)/3
i=j+1=(k+4)/3。
③
③
③当
a
i
,
j
a_{i,j}
ai,j位于上侧次对角线时,就有:
i
=
j
−
1
i=j-1
i=j−1,则此时
i
=
j
−
1
=
(
k
+
2
)
/
3
i=j-1=(k+2)/3
i=j−1=(k+2)/3。
例:若已知下标
k
=
4
k=4
k=4(
k
k
k从
0
0
0开始存储元素),则有:
①
①
①当
a
i
,
j
a_{i,j}
ai,j位于主对角线时,就有:
i
=
j
i=j
i=j,则此时
i
=
j
=
7
/
3
i=j=7/3
i=j=7/3,此时并非整数,故舍去。
②
②
②当
a
i
,
j
a_{i,j}
ai,j位于下侧次对角线时,就有:
i
=
j
+
1
i=j+1
i=j+1,则此时
i
=
j
+
1
=
8
/
3
i=j+1=8/3
i=j+1=8/3,此时并非整数,故舍去。
③
③
③当
a
i
,
j
a_{i,j}
ai,j位于上侧次对角线时,就有:
i
=
j
−
1
i=j-1
i=j−1,则此时
i
=
j
−
1
=
6
/
3
=
2
i=j-1=6/3=2
i=j−1=6/3=2,此时是整数,故即为答案。
故而下标
k
=
4
k=4
k=4所对应的矩阵元素是
a
2
,
3
a_{2,3}
a2,3。
5.3稀疏矩阵
设在 m n mn mn的矩阵中有 t t t个非零元素,令稀疏因子 c = t / ( m n ) c=t/(mn) c=t/(mn),当 c < = 0.05 c<=0.05 c<=0.05时称为稀疏矩阵。
5.3.1三元组表存储
存储思想:除了存储非零元的值之外,还必须同时记下它所在行和列的位置(i,j),反之,一个三元组
(
i
,
j
,
a
i
j
)
(i,j,a_{ij})
(i,j,aij)唯一确定了矩阵的一个非零元,因此,稀疏矩阵可由表示非零元的三元组及其行列数唯一确定。而由上述三元组表示的不同表示方法可引出稀疏矩阵不同的压缩存储方法。
a.三元组顺序表
假设以顺序结构来表示三元组表,则可得到稀疏矩阵的一种压缩存储方式,称为三元组顺序表。
#define MAXSIZE 12500
typedef struct{
int i,j; //该元素的行下标和列下标
ElemType e;
}Triple;
typedef struct{
Triple data[MAXSIZE];
int mu,nu,tu; //矩阵的行数、列数和非零元个数
}TSMatrix;
b.行逻辑链接的顺序表
为了便于随机存取任意行的非零元,则需要知道每一行的第一个非零元在三元组表中的位置,为此可创建指示“行”信息的辅助数组,并固定在稀疏矩阵的存储结构中,称这种“带行链接信息”的三元组表为行逻辑链接的顺序表,类型描述为:
typedef struct{
Triple data[MAXSIZE]; //非零三元表
int rpos[MAXRC]; //各行第一个非零元的位置表
int mu,nu,tu; //矩阵的行数、列数和非零元的个数
}
c.链式存储三元组:十字链表
当矩阵的非零元个数和位置在操作过程中变化较大时,就不宜采用顺序存储结构来表示三元组的线性表,此时采用链式存储结构——十字链表,较为合适。
结点结构:
- row:行
- col:列
- value:值
- down:列指针,指向同一列下一个非零元素。
- right:行指针,指向同一行下一个非零元素。
存储结构:
5.4广义表
5.4.1基本概念
广义表,又称列表,是由n(n>=0)个元素组成的有穷序列:LS={a1,a2,a3,…,an},其中ai可以是原子项,也可以是广义表,分别称为是LS的原子和子表,定义:
- 表头:表中第一个元素a1.
- 表尾:其余元素组成的子表(a2,a3,…,an)。
- 表长:广义表中所包含的元素个数。
- 表深(度):表中括号的最大层数。
注意:空表的表深也是1,图有点问题。
A
=
(
)
A=()
A=()
B
=
(
e
)
B=(e)
B=(e)
C
=
(
a
,
(
b
,
c
,
d
)
)
C=(a,(b,c,d))
C=(a,(b,c,d))
D
=
(
(
)
,
(
e
)
,
(
a
,
(
b
,
c
,
d
)
)
)
D=((),(e),(a,(b,c,d)))
D=((),(e),(a,(b,c,d)))
F
=
(
(
)
)
F=(())
F=(())
E
=
(
a
,
E
)
E=(a,E)
E=(a,E)
其中,E是一个递归的表,长度为2,相当于一个无限的列表E=(a,(a,(a,…)))。
任何一个非空列表其表头可能是原子,也可能是列表,而其表尾必定是列表,如:
G
e
t
H
e
a
d
(
B
)
=
e
,
G
e
t
T
a
i
l
(
B
)
=
(
)
GetHead(B)=e,GetTail(B)=()
GetHead(B)=e,GetTail(B)=()
G
e
t
H
e
a
d
(
D
)
=
A
,
G
e
t
T
a
i
l
(
D
)
=
(
B
,
C
)
GetHead(D)=A,GetTail(D)=(B,C)
GetHead(D)=A,GetTail(D)=(B,C)
,由于(B,C)为非空列表,则可继续分解得到:
G
e
t
H
e
a
d
(
(
B
,
C
)
)
=
B
,
G
e
t
T
a
i
l
(
(
B
,
C
)
)
=
(
C
)
GetHead((B,C))=B,GetTail((B,C))=(C)
GetHead((B,C))=B,GetTail((B,C))=(C)
注意,列表()与(())不同,前者是空表,后者长度为1,分解得到表头与表尾均为(),且,表头是元素,而表尾是一个表。
5.4.2广义表的存储结构
第六章.树和二叉树
6.1基本概念
6.1.1树的定义
6.1.2基本术语
6.2二叉树的概念
6.2.1二叉树的定义
6.2.2特殊的二叉树
6.2.3二叉树的性质
6.2.4二叉树的存储结构
6.3二叉树的遍历
先/中/后序遍历
二叉树的遍历是指按指定的规律对二叉树中的每个结点访问一次且仅访问一次。
- 先序遍历:
- 访问根结点
- 先序访问左子树
- 先序访问右子树
- 中序遍历:
- 访问根结点
- 中序访问左子树
- 中序访问右子树
- 后序遍历:
- 访问根结点
- 后序访问左子树
- 后序访问右子树
以下是表达式为(a+b*(c-d)-e/f)的二叉树:
先序遍历结果(前缀/波兰表达式):
−
+
a
∗
b
−
c
d
/
e
f
-+a*b-cd/ef
−+a∗b−cd/ef
中序遍历结果(中缀表达式):
a
+
b
∗
c
−
d
−
e
/
f
a+b*c-d-e/f
a+b∗c−d−e/f
后序遍历结果(后缀/逆波兰表达式):
a
b
c
d
−
∗
+
e
f
/
−
abcd-*+ef/-
abcd−∗+ef/−
无论是哪种次序的遍历,对有n个结点的二叉树,其时间复杂度均为O(n)。
层序遍历
由遍历序列构造二叉树
由二叉树的先/中/层序遍历和中序遍历可唯一确定一棵二叉树。
6.4线索二叉树
先序线索二叉树找不到先序前驱,后序线索二叉树找不到后序后继。
6.5树的存储
6.5.1顺序存储:双亲表示法
用一组连续的存储空间来存储树的结点,同时在每个结点中增设一个伪指针,指示其双亲结点在数组中的位置,根结点下标为0,其伪指针域为-1。
6.5.2链式结构:孩子表示法
6.5.3孩子链表法
使用孩子链表法时,由于树中每个结点可有多棵子树,即每个结点有多个指针域,每个指针指向其一棵子树的根结点,此时链表中的结点可有如下两种结点格式:
若采用第一种结点格式,则多重链表中的结点是同构的,其中d为树的度。不难推出,在一棵有n个结点、度为k的树中,必有n*(k-1)+1个空链域。
若采用第二种结点格式,则多重链表中的结点是不同构的,其中d是结点的度,degree的值等于d,此时虽没有多余的指针域,但操作不便。
6.5.3孩子兄弟表示法
6.6树、森林与二叉树的转化
6.7树和森林的遍历
6.8哈夫曼树
6.8.1基本概念
- 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径。
- 路径长度:结点路径上的分支数目。
- 树的路径长度:从树根到每一个结点的路径长度之和。
- 结点的带权路径长度:从该结点到树的根结点之间的路径长度与结点的权值乘积
6.8.2哈夫曼树的构造
构造规范:
- 构造哈夫曼树时,为了规范,规定权值小的二叉树作为新构造二叉树的左子树,权值大的二叉树作为新构造的二叉树的右子树。
- 在取值相等时,深度小的二叉树作为新构造的二叉树的左子树,深度大的二叉树作为新构造的二叉树的右子树。
6.8.3哈夫曼编码
固定长度和可变长度编码
- 固定长度编码:对于待处理的一个字符串序列,如果对每个字符采用同样长度的二进制位来表示,则称这种编码为固定长度编码。
- 可变长度编码:若允许对不同字符用不等长的二进制位表示,则称为可变长编码,其特点是对频率高的字符赋予短编码,而对频率低的字符赋予较长编码,从而使字符平均编码长度减短,起到压缩数据的效果。
哈夫曼编码的概念
哈夫曼编码是一种有效的数据压缩编码。
第七章.图
7.1图的定义
有向图与无向图
简单图与多重图
完全图
子图与生成子图
连通、连通图和连通分量
强连通图、强连通分量
生成树、生成森林
顶点的度、入度和出度
边的权和网
路径、路径长度和回路
简单路径、简单回路
距离
有向树
7.2图的存储及基本操作
7.2.1邻接矩阵法
对于n个顶点的图,用一维数组wexs[n]存储顶点信息,用二维数组A[n][n]存储顶点之间关系的信息,该二维数组称为邻接矩阵。
在邻接矩阵中,以顶点在vexs数组中的下标代表顶点,邻接矩阵中的元素A[i][j]存放的是顶点i到顶点j之间关系的信息。
无向无权图的邻接矩阵
无向无权图G=(V,E)有n(n>=1)个顶点,其邻接矩阵是n阶对称方阵,若
(
v
i
,
v
j
)
∈
E
(v_i,v_j)∈E
(vi,vj)∈E,则A[i][j]=1,否则A[i][j]=0:
无向带权图的邻接矩阵
对于带权图而言,若顶点vi和vj之间有边相连,则邻接矩阵中对应项存放着该边对应的权值,若不相连,则用∞来代表这两个顶点之间不存在边。
对于有向图则同理,当无权时,使用0与1来表示是否存在有向边,带权时则用权值或∞来表示是否存在有向边。
7.2.2邻接表法
7.2.3十字链表
十字链表是有向图的另一种链式存储结构,是将有向图的正邻接表和逆邻接表结合起来得到的一种链表。在这种结构中,每条弧的弧头结点和弧尾结点都存放在链表中,并将弧结点分别组织到以弧尾结点为头(顶点)结点和以弧头结点为头(顶点)结点的链表中。
- data:数据域。
- 指针域firstin:指向以该顶点为弧头的第一条弧所对应的弧结点。
- 指针域firstout:指向以该顶点为弧尾的第一条弧所对应的弧结点。
- 尾域tailvex:指示弧尾顶点在图中的位置。
- 头域headvex:指示弧头顶点在图中的位置。
- 指针域hlink:指向弧头相同的下一条弧。
- 指针域tlink:指向弧尾相同的下一条弧。
- Info域:边的权值。
上图中省去了表结点的Info域,可见,从一个顶点结点的firstout出发,沿表结点的tlink指针构成了正邻接表的链表结构,而从一个顶点结点的firstin出发,沿表结点的hlink指针构成了逆邻接表的链表结构。
7.2.4邻接多重表
- data:数据域。
- 指针域firstedge:指向依附于该顶点的第一条边所对应的表结点。
- 标志域mark:用以标识该条边是否被访问过。
- ivex和jvex:分别保存该边所依附的两个顶点在图中的位置。
- info域:边的权值。
- 指针域ilink:指向下一条依附于顶点ivex的边。
- 指针域jlink:指向下一条依附于顶点jvex的边。
7.3图的遍历
7.3.1广度优先搜索
7.3.2深度优先遍历
7.3.3图的遍历与图的连通性
7.4图的应用
7.4.1最小生成树
Prim算法
Kruskal算法
7.4.2最短路径
广度优先搜索查找最短路径只能针对无权图,当图是带权图时,把从一个
第八章.查找
8.1查找的基本概念
8.2顺序查找与折半查找
顺序查找
从表的一端开始逐个将记录的关键字和给定的K值进行比较,若某个记录的关键字和K值相等,则查找成功;否则,若扫描完整个表,仍然没有找到相应的记录,则查找失败。
查找成功时的平均查找长度ASL:
查找失败时,与表中各关键字的比较次数显然是(n+1)次,即查找失败时的平均查找长度为:
A
S
L
失败
=
n
+
1
ASL_{失败}=n+1
ASL失败=n+1
若成功与不成功的概率相等,对每个记录的查找概率为P=1/(2n),则平均查找长度ASL:
A
S
L
=
1
/
(
2
n
)
∑
i
=
1
n
(
n
−
i
+
1
)
+
(
n
+
1
)
/
2
=
3
(
n
+
1
)
/
4
ASL=1/(2n)\sum^n_{i=1}(n-i+1)+(n+1)/2=3(n+1)/4
ASL=1/(2n)∑i=1n(n−i+1)+(n+1)/2=3(n+1)/4
有序表的顺序查找
设表L按关键字从小到大排列,待查找元素的关键字为key,当查找到第i个元素时,发现第i个元素对应的关键字小于key,而第i+1个元素对应的关键字大于key,这时可返回查找失败的信息,因为第i个元素之和的关键字均大于key,所以表中不存在关键字为key的元素。
树中的圆形结点表示存在的元素,矩形结点为失败结点,若有n个结点,则相应有(n+1)个失败结点,查找到失败结点则说明查找失败。由于失败结点本质上并不存在,所以到达失败结点时所查找的长度等于它上面一个圆形结点所在层数,故查找失败时的平均查找长度为:
折半查找
折半查找仅适用于有序的顺序表。
若序列有n个元素,则对应判定树有n个圆形的非叶结点和(n+1)个方形的叶结点,判定树自身是一棵平衡二叉树。从判定树上可见,和给定值进行比较的关键字个数恰好是该路径上结点数或该结点在判定树上的层次数。
类似的,找到有序表中任意记录的过程就是走了一条从根结点到与该记录相应的结点的路径,和给定值之间比较的关键字的个数,恰为该结点在判定树上的层次数,在查找成功时,和给定值比较的关键字个数最多为树的深度
⌊
l
o
g
2
n
⌋
+
1
\lfloor log_2n \rfloor+1
⌊log2n⌋+1
8.3分块查找
分块查找又称索引顺序查找,吸取了顺序查找和折半查找各自的优点,既有动态结构,又属于快速查找。
查找思想:
- (1)将查找表分为几块,块间有序,块内无序,即第一个块中的最大关键字小于第二个块中的所有记录的关键字,并以此类推。
- (2)查找表的基础上建立一个索引表,索引表中记录由各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。
- (3)分块查找过程分为两步,即先在索引表中确定待查记录所在的块,可以顺序查找或折半查找索引表,第二步是在块内顺序查找。
8.4树型查找
8.4.1二叉排序树
8.4.2平衡二叉树
平衡二叉树的插入过程的前半部分与二叉排序树相同,但在新结点插入后,若造成查找路径上的某个结点不再平衡,则需要作出相应的调整,有以下四种情况:
8.5B树和B+树
8.5.1B树
8.5.2B+树
8.6散列表
8.6.1散列表的基本概念
第九章.排序
9.1插入排序
9.1.1直接插入排序
都分为比较关键字和移动关键字,而比较次数与移动次数都取决于待排序表的初始状态。
最好情况下,表中元素已经有序,此时每插入一个元素都只需要比较一次而不需要移动元素,因此时间复杂度为O(n)。
最坏情况下表中元素顺序刚好与排序结果中元素顺序相反,时间复杂度为O(n2),与平均情况下相同。
直接插入排序算法是一种稳定的排序方法,适用于顺序存储和链式存储的顺序表。
9.1.2折半插入排序
9.1.3希尔排序
9.2交换排序
9.2.1冒泡排序
- 空间效率:仅适用了常数个辅助单元,因而空间复杂度为O(n)。
- 时间效率:当初始序列有序时,第一趟冒泡结束后可直接跳出循环,比较次数为(n-1),故最好情况下时间复杂度为O(n);当初始序列为逆序时,需进行(n-1)趟排序,第i趟排序需进行(n-i)次关键字比较,故共比较n(n-1)/2次。
9.2.2快速排序
9.3选择排序
9.3.1简单选择排序
9.3.2堆排序
9.4归并排序与基数排序
9.4.1归并排序