目录
串
- 零个或多个任意字符组成的有限序列
- 串中的数据元素只能是字符
- 一个串中任意个连续字符组成的子序列(含空串)称为该串的子串;真子串是指不包含自身的所有子串
- 主串:包含子串的串相应地称为主串
- 字符位置:字符在序列中的序号为该字符在串中的位置
- 子串位置:子串第一个字符在主串中的位置
- 空格串:由一个或多个空格组成的串,与空串不同
- 串相等:如果两个串每个位置的串值对应相等(相同),称这两个串相等
- 所有的空串是相等的
案例引入
病毒感染检测 P106
人的DNA序列是线性的,而病毒的DNA序列是环状的
串的类型定义
ADT String {
数据对象:D = { ai|ai∈CharacterSet, i=1,2,…,n, n ≥0 }
数据关系:R = {<ai-1, ai>| ai-1, ai∈D, i=2,3,…,n }
基本操作:
StrAssign(t , chars)
初始条件: chars是一个字符串常量。
操作结果:生成一个值为chars的串t 。
StrConcat(s, t)
初始条件:串s, t 已存在。
操作结果:将串t联结到串s后形成新串存放到s中。
StrLength(t)
初始条件:字符串t已存在。
操作结果:返回串t中的元素个数,称为串长。
SubString (s, pos, len, sub)
初始条件:串s, 已存在, 1≦pos≦StrLength(s)且 0≦len≦StrLength(s) –pos+1。
操作结果:用sub返回串s的第pos个字符起长度为len的子串。
……
} ADT String
串的存储结构
顺序存储结构
#define MAXLEN 255
typedef struct{
char ch[MAXLEN+1]; //存储串的一维数组
int length; //串的当前长度
}SString;
链式存储结构(块链结构)
优点:操作方便; 缺点:存储密度较低
可以将多个字符存放在一个结点中,以克服其缺点
#define CHUNKSIZE 80 //块的大小可由用户定义;一个块存放80个字符
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head,*tail; //串的头指针和尾指针
int curlen; //串的当前长度
}LString;
串的模式匹配算法
确定主串中所含子串(模式串)第一次出现的位置(定位)
BF算法 O(m*n)
i的回溯可以表示为(i-j+1)+1
匹配成功之后,子串在主串中第一次出现的位置为i-T.length
int Index_BF(SString S,SString T,int pos){
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+1+1; j = 1;
}
}
if(j > T.length) return i-T.length; //返回匹配的第一个字符的下标
else return 0; //模式匹配不成功
}
若n为主串长度,m为子串长度
总共比较次数:(n-m)*m+m = (n-m+1)*m
若m << n,则算法复杂度为O(m*n)
KMP算法 O(m+n)
主串S的指针i不必回溯,可提速到O(m+n)
int Index_KMP(SString S,SString T,int pos){
int 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]; //模式串向后移动
}
}
if(j > T.length) return i-T.length; //返回匹配的第一个字符的下标
else return 0; //模式匹配不成功
}
计算NEXT函数值
void get_next(SString T,int next[]){
//求模式串T的next函数值并存入数组next
i = 1; next[1] = 0; j = 0;
while(i < T.length){
if(j == 0 || T.ch[i] == T.ch[j]){
++i;++j;
next[i] = j;
}else
j = next [j];
}
}
计算NEXTVAL函数修正值
void get_nextval(SString T, int nextval[]){
//求模式串T的next函数修正值并存入数组nextval
i = 1; nextval[1] = 0; j = 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];
}
else j = nextval[j];
}
}
数组
数组:按一定格式排列起来的具有相同类型的数据元素的集合
一维数组:若线性表中的数据元素为非结构的简单元素,则称为一维数组(线性结构)
数组的顺序存储
数组的特点:
结构固定--维数和维界不变;
数组的基本操作:初始化、销毁、取元素、修改元素值;一般不做插入和删除操作。所以,一般都是采用顺序存储结构来表示数组
二维数组的存储
若二维数组(Aij)m*n 的每个元素占用的存储单元数为L个, LOC[a00]表示元素a11的首地址,即数组的首地址
行优先顺序存储
①第1行中的每个元素对应的(首)地址是:
LOC[a1j] = LOC[a11] + j*L j=1,2, …,n
②第2行中的每个元素对应的(首)地址是:
LOC[a2j]=LOC[a10]+ n*L + j*L j=1,2, …,n
③第m行中的每个元素对应的(首)地址是:
LOC[anj]=LOC[a00]+(n*j+j)*L j=1,2, …,n
三维数组的存储按页/行/列 存放,页优先的顺序存储
a[m1][m2][m3]各维元素个数为m1,m2,m3
Loc(i1,i2,i3) = a + i1*m2*m3 + i2*m3 + i3
特殊矩阵的压缩存储
- 矩阵的常规存储的特点:可以对其元素进行随机存取;矩阵元素非常简单;存储的密度为1
- 不适宜常规存储的矩阵:值相同的元素很多且呈某种规律分布;零元素多
- 矩阵的压缩存储:为多个相同的非零元素只分配一个存储空间;对零元素不分配空间
对称矩阵
存储方法:只存储下(上)三角(包括主对角线)的数据元素,共占用n(n+1)/2个元素空间
可以以行序为主序将元素存在在一个一维数组sa[n(n+1)/2]中
元素在矩阵中的位置aij,其存放在下标以0开始的一维数组中,存放的位置k=[i(i-1)]/2+(j-1)
三角矩阵
对角线以上(以下)的数据元素怒(不包括对角线)全部为常数c
重复元素c共享一个元素存储空间,共占用n(n+1)/2+1个元素空间
右上三角矩阵:k = (i-1)*(2n-i+2)/2+j-i+1 i<=j; k=n(n+1)/2+1 i>j
左下三角矩阵:k = i*(i-1)/2+j i>=j; k=n(n+1)/2+1 i<j
对角矩阵(非零元素都集中在以主对角线为中心的带状区域中)
以主对角线为0
稀疏矩阵的存储
稀疏矩阵的三元组存储
- 对于稀疏矩阵,采用压缩存储方法,只存储非0元素。必须存储非零元素的行下标值、列下标值、元素值。因此,一个三元组(i,j,aij)唯一确定稀疏矩阵的一个非零元素
- 三元组结点定义
#define MAX_SIZE 101
typedef int elemtype;
typedef struct{
int row; //行下标
int col; //列下标
elemtype value; //元素值
}Triple;
- 三元组顺序表定义
typedef struct{
int rn; //行数
int cn; //列数
int tn; //非零元素个数
Triple data[MAX_SIZE];
}TMatrix;
三元组顺序表的优点:非零元素在表中按行序有序存储,因此便于进行依次顺序处理的矩阵运算
三元素顺序表的缺点:不能随机存取,若按行号存取某一行中的非零元,则需从头开始进行查找
稀疏矩阵的链式存储结构---十字链表
优点: 它能够灵活地插入因运算而产生的新的非零元素,删除因运算而产生的新的零元素,实现矩阵的各种运算
typedef struct Clnode{
int row,col; //行号和列号
elemtype value; //元素值
struct Clnode *down, *right;//分别指示同一列中的下一个非零元素和同一行中的下一个非零元素
}OLNode; //非0元素结点
广义表
- 线性表定义为n(n≧0 )个元素a1, a2 ,…, an的有穷序列,该序列中的所有元素具有相同的数据类型且只能是原子项(Atom)。所谓原子项可以是一个数或一个结构,是指结构上不可再分的
- 习惯上,原子用小写字母,子表用大写字母
若广义表LS非空时:
◆ a1(表中第一个元素)称为表头;head(LS) = a1 表头可能是一个原子,也可能是一个子表
◆ 其余元素组成的子表称为表尾;tail(LS) = (a2,a3,…,an) 表尾一定是一个表,加个()
◆ 广义表中所包含的元素(包括原子和子表)的个数称为表的长度。
◆ 广义表中括号的最大层数称为表深 (度) - 广义表可以被其它广义表所共享,也可以共享其它广义表。广义表共享其它广义表时通过表名引用A=() B=(())也就是B(A)
- 广义表本身可以是一个递归表,递归表的深度是无穷值,长度是有限值
广义表的链式存储结构
- 一类是表结点,用来表示广义表项,由标志域,表头指针域,表尾指针域组成
- 另一类是原子结点,用来表示原子项,由标志域,原子的值域组成
- 只要广义表非空,都是由表头和表尾组成。即一个确定的表头和表尾就唯一确定一个广义表
typedef struct GLNode{
int tag; //标志域,为1:表结点; 为0:原子结点
union { //共用体
elemtype value; //原子结点的值域
struct {
struct GLNode *hp, *tp;
}ptr; //ptr和atom两成员公用
}Gdata;
}GLNode; //广义表结点类型
对于上述存储结构,有以下特点:
①若广义表为空,表头指针为空;否则,表头指针总是指向一个表结点,其中hp指向广义表的表头结点(或为原子结点,或为表结点);tp指向广义表的表尾(表尾为空时,指针为空,否则必为表结点)
②表结点太多,会造成空间浪费。可以使用以下结点