文章目录
1、串
KaTeX parse error: Undefined control sequence: \qquadd at position 1: \̲q̲q̲u̲a̲d̲d̲串(string)是零个或者多个任意字符组成的有限序列。
\qquad
子串:一个串中任意个连续字符组成的子序列(含空串)称为该串的子串。
\qquad
真子串:指不包含自身的所有子串。
\qquad
主串:包含子串的串相应地称为主串。
\qquad
字符位置:字符在序列中的序号为该字符在串中的位置。
\qquad
子串位置:子串第一个字符在主串中的位置。
\qquad
空格串:有一个或者多个空格组成的串,与空串不同。
\qquad
串相等:当且仅当两个串的长度相等,并且各个对应位置上的字符都相同时,这两个串才是相等的。所有的空串都是相等的。
\qquad
串的应用非常广泛,计算机上的非数值处理的对象大部分是字符串数据,例如:文字编辑,符号处理,各种信息处理系统等。字符串匹配等。
1.1 串的类型定义,存储结构及运算
\qquad
串的抽象数据类型定义如下所示:
\qquad
串中元素逻辑关系与线性表的相同,串可以采用与线性表相同的存储结构。串可以使用顺序存储结构或者链式存储结构来实现。
1.1.1 串的顺序存储结构
\qquad 串的顺序存储结构
#define MAXLEN 255
class SString
{
char ch[MAXLEN+1]; //存储串的一维数组
int length; //串的当前长度
};
\qquad
串的链式存储结构
\qquad
在进行串的链式存储时,可以多个字符放在一个结点中,克服链式存储存储密度低的缺点。
#define CHUNKSIZE 80
class Chunk
{//块链中的块定义
char ch[CHUNKSIZE];
Chunk *next;
};
class LString
{
Chunk *head, *tail; //串的头指针和尾指针
int curLen; //串的当前长度
};
1.1.2 串的模式匹配算法
\qquad
算法目的: 确定主串中所含的子串(模式串)第一次出现的位置(定位)。
\qquad
算法应用: 搜索引擎,拼写检查,语言翻译,数据压缩等
\qquad
算法种类:
\qquad
BF算法(Brute-Force),又称古典的,经典的,朴素的,穷举的,即暴力破解法
\qquad
KMP算法,算法速度快,但是逻辑较复杂
1.1.2.1 BF算法
\qquad BF算法的思路为穷举思路,算法从主串的每一个字符开始依次与子串的字符进行匹配,直到匹配到子串或者主串结束为止。
int Index_BF(SString S, SString T, int pos = 1)
{
int i = pos, j = 1; //假设字符串第一个字符不放置元素
while(i < S.length && j < T.length)
{
if(S.ch[i] == T.ch[j]){++i;++j}
else
{//主串回溯到下一个为止,子串置为初始位置
i = i - j + 2;
j = 1;
}
if(j >= T.length) return i - T.length;
else return 0;
}
}
\qquad
BF算法的时间复杂度:
\qquad
假设主串长度为n,子串长度为m,则最好情况下需要比较m次,即主串第一个子串即为目标子串
O
(
m
)
O(m)
O(m);最坏情况下,主串最后一个子串才为目标子串,则前面n-m各个主串元素均需要比较m次,则
O
(
m
∗
(
n
−
m
)
+
m
)
O(m*(n-m)+m)
O(m∗(n−m)+m),若m<<n,则算法的时间复杂度为:
O
(
n
∗
m
)
O(n*m)
O(n∗m)。
1.1.2.2 KMP算法
\qquad
KMP算法时由D.E.Knuth,J.H.Morris和V.R.Pratt共同提出的,简称KMP算法。该算法较BF算法有较大的改进,从而使算法效率有较大提高。
KMP算法的设计思想: 利用已经部分匹配的结果来加快模式串的滑动速度,并且主串S的指针i不需要回溯,子串T中的指针j可以不回到初始位置,可以提速到
O
(
m
+
n
)
O(m+n)
O(m+n)。
\qquad
为了衡量子串T中的指针j回到的位置,定义一个next[j]函数,表名当模式中的第j个字符与珠串中相应字符“失配”时,在模式中需要重新确定和主串中该字符进行比较的字符的位置。
n
e
x
t
[
j
]
=
{
m
a
x
{
k
∣
1
<
k
<
j
,
且
"
p
1
.
.
.
p
k
−
1
=
"
p
j
−
k
+
1
.
.
.
p
j
−
1
"
}
/
/
从
头
开
始
的
k
−
1
个
元
素
和
j
前
面
的
k
−
1
个
元
素
0
,
当
j
=
1
时
1
,
其
他
情
况
next[j]=\begin{cases} max \{ k\ |\ 1<k<j, \ 且"p_1...p_{k-1}="p_{j-k+1}...p_{j-1}"\}//从头开始的k-1个元素和j前面的k-1个元素\\ 0, 当j=1时\\ 1, 其他情况 \end{cases}
next[j]=⎩⎪⎨⎪⎧max{k ∣ 1<k<j, 且"p1...pk−1="pj−k+1...pj−1"}//从头开始的k−1个元素和j前面的k−1个元素0,当j=1时1,其他情况
\qquad
next的例子如下图所示:
\qquad
使用next[j]表的KMP算法如下所示:
int Index_KMP(SString S, SString T, int pos = 1)
{
i = pos, j = 1;
while(i < S.length && j < T.length)
{
if(j==0 || S.ch[i] == T.ch[j]){++i;++j}
else
j = next[j]; //i的位置不变
}
if(j >= T.length) return i - T.lenght; //匹配成功
else return 0; //匹配失败
}
//获取next[j]
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];
}
}
\qquad
next的值还可以进行一定的修正,修正方法如下所示:
\qquad
修正版求next值的代码如下所示:
void Get_next(SString T, int &nextVal[])
{
int i = 1, j = 0;
nextVal[1]=0;
while(i < T.length)
{
if(j == 0 || T.ch[i]==T.ch[j])
{
++i; ++j;
if(T.ch[i]!=T.ch[j])nextVal[i]=j;
else nextVal[i]=nextVal[j];
nextVal[i]=j;
}
else
j = nextVal[j];
}
}
2、数组
\qquad
按照一定格式排列起来的,具有相同类型的数据元素的集合。
\qquad
一维数组:若线性表中的数据元素为非结构的简单元素,则称为一维数组;一维数组的逻辑结构为线性结构,是一个定长的线性表。
\qquad
声明格式: 数据类型,变量名称[长度]
int num[5] = {0,1,2,3,4};
\qquad
二维数组: 若一维数组中的数据元素又是一维数据结构,则称为二维数组。
\qquad
声明格式: 数据类型 变量名称[行数][列数]
int num[5][8];
\qquad
三维数组: 若二维数组中的元素又是一个一维数组,则称作三维数组;
\qquad
n维数组: 若n-1维数组中的元素又是一个一维数组结构,则称为n为数组。
\qquad
线性表结构是数组结构的一个特例,而数组结构又是线性表结构的拓展。 数组特点 是结构固定,定义后,维数和维界不再改变。
\qquad
数组的基本操作:除了结构的初始化和销毁外,只有取元素和修改元素值的操作。
2.1 数组的抽象数据类型定义
\qquad
以二维数组为例,定义数组的抽象数据类型:
2.2 数组的顺序存储结构
\qquad 由于数组的结构固定,维数和维界不变,所以数组一般都是采用顺序存储结构的。数组可以是多维的,但存储数据元素的内存单元地址是一维的,因此,在存储数组结构之前,需要解决将多维关系映射到一维关系的问题。
2.2.1 二维数组
\qquad
二维数组可以有两种顺序存储方式,一种是以行序为主体,低下标优先进行存储(C和Java中使用这种存储方式);另外一种是以列序为主体,高下标优先进行存储。
\qquad
以行序为主序,设数组开始存储的位置为
L
O
C
(
0
,
0
)
LOC(0,0)
LOC(0,0),数组每行有n个元素,存储每个元素需要
L
L
L个存储单元,数组元素
a
[
i
]
[
j
]
a[i][j]
a[i][j]的存储为止为:
L
o
c
(
i
,
j
)
=
L
o
c
(
0
,
0
)
+
(
i
∗
n
+
j
)
∗
L
Loc(i,j)=Loc(0,0)+(i*n+j)*L
Loc(i,j)=Loc(0,0)+(i∗n+j)∗L。
2.2.2 三维数组
\qquad 三维数组可以按页/行/列进行存放,页优先的顺序存储,每一页在按照行优先的顺序进行存储。如三维数组 a [ m 1 ] [ m 2 ] [ m 3 ] a[m_1][m_2][m_3] a[m1][m2][m3]各维的元素个数为: m 1 , m 2 , m 3 m_1,m_2,m_3 m1,m2,m3,则 a [ i 1 ] [ i 2 ] [ i 3 ] a[i_1][i_2][i_3] a[i1][i2][i3]元素的存储位置为: L o c ( i 1 , i 2 , i 3 ) = a + i 1 ∗ m 2 ∗ m 3 + i 2 ∗ m 3 + i 3 Loc(i_1,i_2,i_3)=a+i_1*m_2*m_3+i_2*m_3+i_3 Loc(i1,i2,i3)=a+i1∗m2∗m3+i2∗m3+i3。
2.2.3 n维数组
2.3 特殊矩阵的压缩存储
\qquad
矩阵: 表示一个由
m
×
n
m×n
m×n个元素排成m行n列的表;矩阵的常规存储:将矩阵描述为一个二维数组,矩阵常规存储可以对其元素进行随机存取,矩阵运算非常简单,存储的密度为1。不适宜常规存储的矩阵:值相同的元素很多且呈现某种规律分布;零元素特别多。矩阵的压缩存储:为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。
\qquad
特殊矩阵的例子:如对称矩阵,对角矩阵,三角矩阵,稀疏矩阵等。稀疏矩阵指:矩阵中非零元素的个数较少,一般小于5%。
2.3.1 对称矩阵
\qquad
特点:在n×n的矩阵a中,满足如下性质:
a
i
j
=
a
j
i
(
1
≤
i
,
j
≤
n
)
a_{ij}=a_{ji}(1 \leq i,j \leq n)
aij=aji(1≤i,j≤n)。对称矩阵的存储方法为:只存储下三角(包括主对角线)的数据元素。共占用
n
(
n
+
1
)
/
2
n(n+1)/2
n(n+1)/2个元素空间。
\qquad
对称矩阵中某个元素
a
i
,
j
a_{i,j}
ai,j的存储位置为:
L
o
c
(
0
,
0
)
+
(
i
(
i
−
1
)
2
+
j
)
∗
L
Loc(0,0)+(\frac{i(i-1)}{2}+j)*L
Loc(0,0)+(2i(i−1)+j)∗L,其中
L
L
L表示每一个元素所占用的内存空间。
2.3.2 三角矩阵
\qquad
特点:对角线以下(或者以上)的数据元素(不包括对角线)全部为常数c。
\qquad
存储方法:重复元素c共享一个元素存储单元,共占用
n
(
n
+
1
)
/
2
+
1
n(n+1)/2+1
n(n+1)/2+1个元素空间。
2.3.3 对角矩阵
\qquad
特点:在n×n的方阵中,所有非零元素都集中在以主对角线为中心的带状区域中,区域外的值全为0,则称为对角矩阵。常见的有三对角矩阵,五对角矩阵和七对角矩阵。
\qquad
存储方法为:以对角线的顺序进行存储,如下所示:
2.3.4 稀疏矩阵的存储
\qquad 设在m×n的矩阵中有t个非零元素,令 δ = t / ( m ∗ n ) \delta=t/(m*n) δ=t/(m∗n),当 t ≤ 0.05 t \leq 0.05 t≤0.05时,称为稀疏矩阵。
2.3.4.1 三元组法
\qquad
可以使用三元组法来存储稀疏矩阵,三元组
<
i
,
j
,
a
i
,
j
>
<i,j,a_{i,j}>
<i,j,ai,j>来表示元素所在的行索引,列索引和元素值,之后还需要记录矩阵的总共和行数和列数和矩阵中总共元素的个数。三元组的不同表示方法可以决定稀疏矩阵不同的压缩存储方法。
\qquad
三元组法又称为有序的双下标法,其优点是:非零元素在表中按行序有序存储,因此便于进行依行顺序处理的矩阵运算;其缺点是:不能随机存取。若按照行号存取某一行中的非零元,则需从头开始进行查找。
2.3.4.2 十字链表法
\qquad
十字链表法的优点是:他能够灵活地插入因为运算而产生的新的非零元素,删除因运算而产生的新的零元素,实现矩阵的各种运算。
\qquad
在十字链表中,矩阵的每一个非零元素用一个结点表示,该节点的成员包括:行索引,列索引,元素值,链接同一行中下一个非零元素的指针,链接同一列中下一个非零元素的指针,十字链表的结点结构示意图如下所示:
\qquad
十字链表法的实现还需要一个行的头指针,指向每一行中第一个非零元素;一个列的头指针,指向每一列中第一个非零元素。
3、 广义表
\qquad
广义表是
n
≥
0
n \geq0
n≥0个元素
a
0
,
a
1
,
.
.
.
,
a
n
−
1
a_0,a_1,...,a_{n-1}
a0,a1,...,an−1的有限序列,其中每一个
a
i
a_i
ai或者是原子,或者是一个广义表。
\qquad
广义表的表头是广义表中的第一个元素;广义表的表尾不是广义表的最后一个元素,而是广义表除表头之外的其余元素组成的子表。
3.1 广义表的性质
\qquad
广义表中数据元素有相对次序,一个直接前驱和一个直接后继;
\qquad
广义表的长度定义为最外层所包含的元素的个数,如
C
=
(
a
,
(
b
,
c
)
)
C=(a,(b,c))
C=(a,(b,c))是长度为2的广义表;
\qquad
广义表的深度定义为,该广义表展开之后所包含的括号的重数,原子的深度为0,空表的深度为1;
\qquad
广义表可以为其他广义表所共享;如上述广义表B就共享广义表A。
\qquad
广义表可以是一个递归的表,如
F
=
(
a
,
F
)
F=(a,F)
F=(a,F),递归表的长度为2,深度为无穷。
\qquad
广义表是多层次结构,广义表的元素可以是单元素,也可以是子表,可以用树型结构来形象地进行表示。
3.2 广义表和线性表的区别
\qquad
广义表可以看成线性表的推广,线性表是广义表的特例。
\qquad
当二维数组的每行或者每列作为子表处理时,二维数组即为一个广义表;
\qquad
树和图也可以用广义表来表示;
\qquad
广义表的结构相当灵活,在某种前提下,特可以兼容线性表,数组,树和有向图等各种常用的数据结构。
3.3 广义表的基本运算
\qquad
求表头,即非空广义表的第一个元素,可以是一个原子,也可以是一个子表;
\qquad
求表尾,非空广义表出去表头元素以外其他元素构成的表,表尾一定是一个表。