注:基于101《数据结构》教材
索引
绪论
数据结构的三个要素:数据的逻辑结构、数据的存储结构及操作定义与实现。
数据的四种逻辑结构:集合、线性结构、树形结构、图形结构。
抽象数据类型:使用者无需知道数据元素的类型,只需知道数据元素间的逻辑关系,是与数据元素及在数据元素上的实现无关的数据类型。
数据的操作:包括操作的定义和实现。
操作定义是对现实问题的抽象,独立于计算机;
操作实现依赖于计算机和程序设计语言。
影响时间复杂度的因素:
- 硬件性能
- 编程语言及生成代码的质量
- 问题和数据规模
- 算法设计效率
空间复杂度:
空间消耗包括程序代码所占空间、数据所占空间及中间过程使用的辅助空间,空间复杂度指算法使用的辅助空间与数据规模的关系。
复杂度计算:
顺序组合取复杂度最大值,嵌套、多层取复杂度相乘
线性结构
线性表的定义:由同一类型的数据元素构成的有序序列的线性结构【注意是逻辑结构,物理上不存在】
线性表的逻辑结构:数据元素之间线性的序列关系
线性表的物理结构:线性表在计算机中的存储方式,主要有两种:顺序存储结构和链式存储结构
顺序表
又称顺序表,逻辑顺序与物理顺序相同,都是位置相邻;
可以静态分配(空间事先固定,溢出会导致程序崩溃),也可以动态分配(用malloc
函数,空间占满后另外开辟一块更大的存储空间并替换原来的)
特点:随机访问(随机存取),查找O(1);存储密度高;逻辑上相邻元素物理上也相邻,插入删除需移动大量元素
基本操作(增删改查)注意判断合法性,顺序表可能出现表满溢出的情况
链表
节点之间相邻的概率极小。
缺点:浪费存储空间;
存储结构:非随机存取。
创建链表推荐使用头结点,优点是统一了插入和删除操作。
进行插入、删除时,都需要先找到操作对象的前一个元素,判断合法性后申请或释放对应空间,复杂度都为
O
(
n
)
O(n)
O(n).
静态链表:用数组存放线性表中的元素,给每个数组元素增加一个域,用于指示下一个元素的位置。(数组和链表的结合体)
块状链表:用双向链表维护总长度不超过
n
n
n的线性表各元素,分为若干个包含
n
\sqrt{n}
n个元素的块。(时间复杂度:
O
(
n
)
O(\sqrt{n})
O(n))
广义表:递归定义的线性结构,其中的元素不仅可以是单元素也可以是另一个广义表。
长度:最外层包含的元素个数;
深度:包含括号的重数。
空表长度为0,深度定为1;原子深度为0。
任何一个非空广义表都可以分为表头(原子)和表尾(广义表,最外层一定有括号)
广义表的深度是无穷值,长度是有限值。
多维数组
行优先存放,
n
n
n维数组各维大小为
(
s
1
,
s
2
,
.
.
.
s
n
)
(s_1,s_2,...s_n)
(s1,s2,...sn),第一个元素的地址是
l
(
0
,
0
,
…
,
0
)
l_{(0,0,…,0)}
l(0,0,…,0),元素占用空间为
s
i
z
e
size
size个字节,则下标为
(
i
1
,
i
2
,
…
,
i
n
)
(i_1,i_2,…,i_n)
(i1,i2,…,in)的元素位置是
l
(
0
,
0
,
…
,
0
)
+
(
i
1
∗
s
2
∗
s
3
∗
.
.
.
∗
s
n
+
i
2
∗
s
3
∗
.
.
.
∗
s
n
+
.
.
.
+
i
n
)
∗
s
i
z
e
l_{(0,0,…,0)}+(i_1*s_2*s_3*...*s_n+i_2*s_3*...*s_n+...+i_n)*size
l(0,0,…,0)+(i1∗s2∗s3∗...∗sn+i2∗s3∗...∗sn+...+in)∗size
例题1:
假设有二维数组 A6×8,每个元素用相邻的 6 个字节存储,存储器按字节编址。已知 A 的起始存储位置(基地址)为 1000,分别计算
a
[
3
]
[
6
]
a_{[3][6]}
a[3][6]按行存储和列存储的地址。
答案:1) 1000+6*(3*8+6) 2) 1000+6*(6*6+3) 注意行优先和列优先对应的不同排列方式
数组的下标表示该元素在该维度前的数目,计算地址用基址+偏移量
例题2:
假设按低下标优先存储整数数组A9x3x5x8时第一个元素的字节地址是100,每个整数占四个字节。问下列元素的存储地址是什么?(1)a0000(2)a1111(3)a3125(4)a8247
答案:
LOC(a0000)=100
LOC(a1111)=100+ (3×5×8×1+ 5×8×1+ 8×1+ 1)×4=776
LOC(a3125)=100+ (3×5×8×3+ 5×8×1+ 8×2+ 5)×4=1784
LOC(a8247)=100+ (3×5×8×8+ 5×8×2+ 8×4+ 7)×4=4416
正确理解低下标优先存储:不是指每个维度的元素数目多少,而是下标从左往右的次序
套公式
l
=
l
(
0
,
0
,
…
,
0
)
+
(
i
1
∗
s
2
∗
s
3
∗
.
.
.
∗
s
n
+
i
2
∗
s
3
∗
.
.
.
∗
s
n
+
.
.
.
+
i
n
)
∗
s
i
z
e
l=l_{(0,0,…,0)}+(i_1*s_2*s_3*...*s_n+i_2*s_3*...*s_n+...+i_n)*size
l=l(0,0,…,0)+(i1∗s2∗s3∗...∗sn+i2∗s3∗...∗sn+...+in)∗size即可。
压缩空间存储:上(下)三角矩阵
对于下三角矩阵:
设单个元素所占空间为
s
i
z
e
size
size,则
a
[
i
]
[
j
]
a_{[i][j]}
a[i][j]的存储位置
l
i
j
l_{ij}
lij与矩阵首个元素
a
[
0
]
[
0
]
a_{[0][0]}
a[0][0]的地址
l
00
l_{00}
l00的关系是:
l
i
j
=
l
00
+
(
i
(
i
+
1
)
/
2
+
j
)
×
s
i
z
e
,
i
≥
j
l_{ij} = l_{00} + (i(i+1)/2+j) × size,i≥j
lij=l00+(i(i+1)/2+j)×size,i≥j
对角矩阵:所有非0元集中在以主对角线为中心的带状区域中。关键是见了数组元
S
A
[
k
]
SA_{[k]}
SA[k]与矩阵元
a
i
j
a_{ij}
aij的对应关系
三对角矩阵:
k
=
2
(
i
−
1
)
+
j
−
1
,
(
∣
i
−
j
∣
≤
1
)
k = 2 ( i - 1 ) + j - 1,(| i - j | ≤ 1)
k=2(i−1)+j−1,(∣i−j∣≤1)
稀疏矩阵:0元素数目远多于非0元素数目,存储非0项
顺序存储:三元组表
(
r
o
w
,
c
o
l
,
v
a
l
u
e
)
(row,col,value)
(row,col,value)
链式存储:十字链表
(
r
o
w
,
c
o
l
,
v
a
l
u
e
)
(row,col,value)
(row,col,value),通过行指针right和列指针down串联各行各列
矩阵转置算法,复杂度
O
(
M
.
n
u
+
M
.
t
u
)
O(M.nu+M.tu)
O(M.nu+M.tu)
Status FastTransposeSMatrix(TSMatrix M, TSMatrix &T)
{
T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
if (T.tu) {
for (col=1; col<=M.nu; ++col) num[col] = 0;
for (t=1; t<=M.tu; ++t) // M中每列非零元个数
num[M.data[t].j]++;
// 求第col列中第一个非零元在T.data中的序号
cpot[1] = 1;
for (col=2; col<=M.nu; ++col)
cpot[col] = cpot[col-1] + num[col-1];
for (p=1; p<=M.tu; ++p)
{ /* 转置矩阵元素 */
col = M.data[p].j;
q = cpot[col];
T.data[q].i = M.data[p].j;
T.data[q].j = M.data[p].i;
T.data[q].e = M.data[p].e;
cpot[col]++;
}
}
return OK;
}
该部分考察计算题的可能性较大,同时要掌握基础概念
栈与队列
给定一个入栈序列,序列长度为N,出栈顺序总数为 C 2 N N N + 1 \frac{C_{2N}^{N}}{N+1} N+1C2NN.
顺序栈
溢出:分为上溢(栈满进栈)和下溢(栈空出栈)
特点:应用广泛,存储开销低,根据栈顶的相对位移定位栈内元素(虽然栈只允许在栈顶操作)
链式栈
时间效率和顺序栈相似,
空间效率上顺序栈结构紧凑,但必须事先确定长度;链式栈长度可变,增加结构性开销
顺序队列
溢出:上溢(队满进队),下溢(队空出队),假溢出(队尾指针达到最大值但队前端有空闲位置,入队时溢出)
用两个指针Q.front和Q.rear维护队列,其中Q.rear有实指和虚指两种形式
普通队列->循环队列
实现顺序队列时要注意的点:
- 若存储ksize个,则需要开辟ksize+1个空间(浪费一个存储空间区分空和满)
- 入队时先判断队列是否满,改变且仅改变尾指针, queue.rear = (queue.rear + 1)%queue.capacity // 循环后继
- 出队时先判断队列是否空,改变且仅改变头指针,queue.front = (queue.front +1) % queue.capacity
环形队列
队列空:Q.rear == Q.front
队列满:(Q.rear+1) mod M == Q.front
链式队列
顺序队列存储空间固定,链式队列不固定,但都不允许访问队列内部元素
可以用两个底部相连的栈模拟双端队列(插入删除在线性表两端进行)
超队列:删除在一端进行,插入在两端进行
超栈:插入在一端进行,删除在两端进行
栈的应用
后缀表达式求值
依次读入后缀表达式,若为操作数则压入栈中,若为运算符则从栈中两次取出栈顶,运算后压入结果
最终若表达式正确,栈中仅由的一个值即为结果
中缀表达式转后缀表达式
依次读入中缀表达式,若为操作数则直接输出到序列,若为开括号则入栈,若为闭括号先判断栈是否为空(为空则异常),若非空则弹出栈顶元素直至遇到开括号;若为运算符,则在(栈非空 and 栈顶不是开括号 and 栈顶运算符的优先级不低于输入的运算符的优先级)条件下循环,弹出栈顶元素并放到后缀表达式序列中,循环结束后将输入的运算符压入栈内。
单调栈、单调队列
单调栈:栈中元素具有单调性的栈。
单调队列:队列中元素有单调性,入队时要检查单调性,不满足的元素被删除
字符串
存储结构有顺序和链式两种,字符串一般都有结束符,在c和cpp中是'\0'
,不计入字符串长度但占存储空间。
要熟悉基本的字符串函数
concat
:前后拼接 replace
:char *replace (char *source, char *target, char *dest);
substr
:char* substr(char* str, int start, int len); index
:char *index(const char *s, char c);
strsmp
:int strcmp ( const char * str1, const char * str2 )[返回值>0:字符串1>字符串2]
strcpy
:char * strcpy ( char * destination, const char * source );
例题1: 若串S1=‘ABCDEFG’, S2=‘9898’ ,S3=‘###’,S4=‘012345’,执行
concat(replace(S1,substr(S1,length(S2),length(S3)),S3),substr(S4,index(S2,‘8’),length(S2)))
其结果为 ABC###G1234
例题2: 摘自严蔚敏《数据结构》习题集
注意replace函数会将所有在主串中出现的指定子串都替换!
KMP算法求next和nextval数组
BV16a411D7Us
树与二叉树
满二叉树
由度为0和2的结点构成的二叉树,树中没有度为1的结点
完全二叉树
从第1层到第𝑑−2层全是度为2的中间结点;第𝑑层的结点都是叶结点,度为0;在第𝑑−1层,各结点的度从左向右单调非递增排列,同时度为1的结点要么没有,要么只有一个且该结点的左子树非空。
完美二叉树
d-1层所有结点度都为2.
二叉树的性质
- 设非空二叉树中度为𝒊∈[𝟎,𝟐]的结点数为 n i n_i ni ,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
- 第 i i i层最多有 2 i − 1 2^{i-1} 2i−1个结点
- 高度为d的二叉树最多有 2 d − 1 2^{d}-1 2d−1个结点
- 一棵二叉树是完美二叉树的充要条件:有 2 d − 1 2^{d}-1 2d−1个结点
树中结点数等于所有结点度的和+1
整理范围:绪论->树的性质