(1)串的相关概念。
(2)串的顺序存储结构和链式存储结构的优缺点。
(3)顺序串运算算法设计。
(4)链串运算算法设计。
(5) BF模式匹配算法设计。
(6) KMP算法设计,KMP算法是提高串匹配效率的。
(1)数组的顺序存储结构及其元素地址计算方法。
(2)对称矩阵、上三角矩阵、下三角矩阵和三对角矩阵的压缩存储方法。
(3)稀疏矩阵的三元组存储结构及其基本运算算法设计。
(4)稀疏矩阵十字链表存储结构的特点。
(5)广义表的定义和特点。
(6)广义表的链式存储结构及其递归特性。
(7)广义表的递归算法设计方法。
(1)串是若干个字符的有限序列,空串是长度为零的串。
(2)串可以看成是一种特殊的线性表;其逻辑关系为线性关系。
(3)串的长度是指串中所含字符的个数。
(4)含n个不同字符的串的子串个数为n(n+1)/2+1。
(5)串主要有顺序串和链串两种存储结构。
(6)顺序串的算法设计和顺序表类似,链串的算法设计和单链表类似。
(7)在串匹配中一般将主串称为目标串,将子串称为模式串。
(8) BF模式匹配算法中需要回溯,时间复杂度为O(mXn),而KMP 算法消除了回溯, 时间复杂度为 O(m+n)。
广义表
1、广义表的定义:由零个或多个单元素或子表所组成的有限序列。
广义表与线性表的区别在于:线性表的成分都是结构上不可分的单元素,而广义表的成分可以是单元素,也可以是有结构的表。
广义表的长度是指广义表中元素的个数。
广义表的深度是指广义表展开后所含的括号的层数。
#include
using namespace std;
#define MaxSize 100
typedef struct
{
char data[MaxSize];
int length; //串长
} SqString;
void StrAssign(SqString &s,char cstr[]) //字符串常量赋给串s
{
int i;
for (i=0;cstr[i]!='\0';i++)
{
s.data[i]=cstr[i];
}
s.length=i;
}
void DestroyStr(SqString &s)
{ }
void StrCopy(SqString &s,SqString t) //串复制
{
int i;
for (i=0;i<t.length;i++)
{
s.data[i]=t.data[i];
}
s.length=t.length;
}
bool StrEqual(SqString s,SqString t) //判串相等
{
bool same=true;
int i;
if (s.length!=t.length) //长度不相等时返回0
{
same=false;
}
else
{
for (i=0;i<s.length;i++)
{
if (s.data[i]!=t.data[i]) //有一个对应字符不相同时返回0
{ same=false;
break;
}
}
}
return same;
}
int StrLength(SqString s) //求串长
{
return s.length;
}
SqString Concat(SqString s,SqString t) //串连接
{
SqString str;
int i;
str.length=s.length+t.length;
for (i=0;i<s.length;i++) //将s.data[0..s.length-1]复制到str
{
str.data[i]=s.data[i];
}
for (i=0;i<t.length;i++) //将t.data[0..t.length-1]复制到str
{
str.data[s.length+i]=t.data[i];
}
return str;
}
SqString SubStr(SqString s,int i,int j) //求子串
{
SqString str;
int k;
str.length=0;
if (i<=0 || i>s.length || j<0 || i+j-1>s.length)
{
return str; //参数不正确时返回空串
}
for (k=i-1;k<i+j-1;k++) //将s.data[i..i+j]复制到str
{
str.data[k-i+1]=s.data[k];
}
str.length=j;
return str;
}
SqString InsStr(SqString s1,int i,SqString s2) //插入串
{
int j;
SqString str;
str.length=0;
if (i<=0 || i>s1.length+1) //参数不正确时返回空串
{
return str;
}
for (j=0;j<i-1;j++) //将s1.data[0..i-2]复制到str
{
str.data[j]=s1.data[j];
}
for (j=0;j<s2.length;j++) //将s2.data[0..s2.length-1]复制到str
{
str.data[i+j-1]=s2.data[j];
}
for (j=i-1;j<s1.length;j++) //将s1.data[i-1..s1.length-1]复制到str
{
str.data[s2.length+j]=s1.data[j];
}
str.length=s1.length+s2.length;
return str;
}
SqString DelStr(SqString s,int i,int j) //串删去
{
int k;
SqString str;
str.length=0;
if (i<=0 || i>s.length || i+j>s.length+1) //参数不正确时返回空串
return str;
for (k=0;k<i-1;k++) //将s.data[0..i-2]复制到str
str.data[k]=s.data[k];
for (k=i+j-1;k<s.length;k++) //将s.data[i+j-1..s.length-1]复制到str
str.data[k-j]=s.data[k];
str.length=s.length-j;
return str;
}
SqString RepStr(SqString s,int i,int j,SqString t) //子串替换
{
int k;
SqString str;
str.length=0;
if (i<=0 || i>s.length || i+j-1>s.length) //参数不正确时返回空串
{
return str;
}
for (k=0;k<i-1;k++) //将s.data[0..i-2]复制到str
{
str.data[k]=s.data[k];
}
for (k=0;k<t.length;k++) //将t.data[0..t.length-1]复制到str
{
str.data[i+k-1]=t.data[k];
}
for (k=i+j-1;k<s.length;k++) //将s.data[i+j-1..s.length-1]复制到str
{
str.data[t.length+k-j]=s.data[k];
}
str.length=s.length-j+t.length;
return str;
}
void DispStr(SqString s) //输出串s
{
int i;
if (s.length>0)
{ for (i=0;i<s.length;i++)
{
cout<<s.data[i];
}
cout<< endl;
}
}
int index(SqString s,SqString t)
{
int i=0,j=0;
while (i<s.length && j<t.length)
{
if (s.data[i]==t.data[j]) //继续匹配下一个字符
{
i++; //主串和子串依次匹配下一个字符
j++;
}
else //主串、子串指针回溯重新开始下一次匹配
{
i=i-j+1; //主串从下一个位置开始匹配
j=0; //子串从头开始匹配
}
}
if (j>=t.length)
return(i-t.length); //返回匹配的第一个字符的下标
else
return(-1); //模式匹配不成功
}
void GetNext(SqString t,int next[]) //由模式串t求出next值
{
int j,k;
j=0;k=-1;next[0]=-1;
while (j<t.length-1)
{
if (k==-1 || t.data[j]==t.data[k]) //k为-1或比较的字符相等时
{
j++;
k++;
next[j]=k;
}
else
{
k=next[k];
}
}
}
int KMPIndex(SqString s,SqString t) //KMP算法
{
int next[MaxSize],i=0,j=0;
GetNext(t,next);
while (i<s.length && j<t.length)
{
if (j==-1 || s.data[i]==t.data[j])
{
i++;j++; //i,j各增1
}
else j=next[j]; //i不变,j后退
}
if (j>=t.length)
return(i-t.length); //返回匹配模式串的首字符下标
else
return(-1); //返回不匹配标志
}
void GetNextval(SqString t,int nextval[]) //由模式串t求出nextval值
{
int j=0,k=-1;
nextval[0]=-1;
while (j<t.length)
{
if (k==-1 || t.data[j]==t.data[k])
{
j++;
k++;
if(t.data[j]!=t.data[k])
{
nextval[j]=k;
}
else
{
nextval[j]=nextval[k];
}
}
else
{
k=nextval[k];
}
}
}
int KMPIndex1(SqString s,SqString t) //修正的KMP算法
{
int nextval[MaxSize],i=0,j=0;
GetNextval(t,nextval);
while (i<s.length && j<t.length)
{
if (j==-1 || s.data[i]==t.data[j])
{
i++;j++;
}
else j=nextval[j];
}
if (j>=t.length)
return(i-t.length);
else
return(-1);
}
(1)串是一种特殊的线性表,其特殊性体现在( )。
A.可以顺序存储
B.数据元素是一个字符
C.可以链式存储
D.数据元素可以是多个字符若
答案:B
(2)串下面关于串的的叙述中,( )是不正确的。
A.串是字符的有限序列
B.空串是由空格构成的串
C.模式匹配是串的一种重要运算
D.串既可以采用顺序存储,也可以采用链式存储
答案:B
解释:空格常常是串的字符集合中的一个元素,有一个或多个空格组成的串成为空格串,零个字符的串成为空串,其长度为零。
(3)串“ababaaababaa”的 next 数组为( )。
A . 012345678999
B . 012121111212
C . 011234223456
D.0123012322345
答案:C
(4)串“ababaabab”的 nextval 为( )。
A . 010104101
B . 010102101
C . 010100011
D.010101011
答案:A
(5)串的长度是指( )。
A.串中所含不同字母的个数
B.串中所含字符的个数
C.串中所含不同字符的个数
D.串中所含非空格字符的个数
答案:B
解释:串中字符的数目称为串的长度。
(6)假设以行序为主序存储二维数组 A=array[1…100,1…100],设每个数据元素占 2 个存储单元,基地址为 10,则 LOC[5,5]=( )。
A . 808
B . 818
C . 1010
D.1020
答案:B
解释:以行序为主,则 LOC[5,5]=[(5-1)*100+(5-1)]*2+10=818。
(7)设有数组 A[i,j],数组的每个元素长度为 3 字节,i 的值为 1 到 8,j 的值为 1 到 10,数组从内存首地址 BA 开始顺序存放,当用以列为主存放时,元素 A[5,8]的存储首地址为( )。
A . BA+141
B . BA+180
C . BA+222
D. BA+225
答案:B
解释:以列序为主,则 LOC[5,8]=[(8-1)*8+(5-1)]*3+BA=BA+180。
(8)设有一个 10 阶的对称矩阵 A,采用压缩存储方式,以行序为主存储,a11 为第一元素,其存储地址为 1,每个元素占一个地址空间,则 a85 的地址为()。
A.13
B.32
C.33
D.40
答案:C
(9)若对 n 阶对称矩阵 A 以行序为主序方式将其下三角形的元素(包括主对角线上所有元素)依次存放于一维数组 B[1…(n(n+1))/2]中,则在 B 中确定
aij(i A . i*(i-1)/2+j B . j*(j-1)/2+i C . i*(i+1)/2+j D.j*(j+1)/2+i 答案:B (10)二维数组 A 的每个元素是由 10 个字符组成的串,其行下标 i=0,1,…,8,列下标 j=1,2,…,10。若 A 按行先存储,元素 A[8,5]的起始地址与当 A 按列先存储时的元素( )的起始地址相同。设每个字符占一个字节。 A.A[8,5] B.A[3,10] C. A[5,8] D.A[0,9] 答案:B 解释:设数组从内存首地址 M 开始顺序存放,若数组按行先存储,元素 A[8,5]的起始地址为:M+[(8-0)*10+(5-1)]*1=M+84;若数组按列先存储,易计算出元素 A[3,10]的起始地址为:M+[(10-1)*9+(3-0)]*1=M+84。故选 B。 (11)设二维数组 A[1… m,1… n](即 m 行 n 列)按行存储在数组 B[1…m*n]中,则二维数组元素 A[i,j]在一维数组 B 中的下标为( )。 A.(i-1)*n+j B.(i-1)*n+j-1 C.i*(j-1) D.j*m+i-1 答案:A 解释:特殊值法。取 i=j=1,易知 A[1,1]的的下标为 1,四个选项中仅有 A 选项能确定的值为 1,故选 A。 (12)数组 A[0…4,-1…-3,5…7]中含有元素的个数( )。 A.55 B.45 C.36 D.16 答案:B 解释:共有 533=45 个元素。 (13) 广 义 表 A=(a,b,(c,d),(e,(f,g))), 则 Head(Tail(Head(Tail(Tail(A))))) 的值为( )。 A.(g) B.(d) C.c D.d 答案:D 解 释 : Tail(A)=(b,(c,d),(e,(f,g))) ; Tail(Tail(A))=( (c,d),(e,(f,g))) ; Head(Tail(Tail(A)))= (c,d) ; Tail(Head(Tail(Tail(A))))=(d) ;Head(Tail(Head(Tail(Tail(A)))))=d。 (14)广义表((a,b,c,d))的表头是( ),表尾是( )。 A.a B.( ) C.(a,b,c,d) D.(b,c,d) 答案:C、B 解释:表头为非空广义表的第一个元素,可以是一个单原子,也可以是一个子表,((a,b,c,d))的表头为一个子表(a,b,c,d);表尾为除去表头之外,由其余元素构成的表,表为一定是个广义表,((a,b,c,d))的表尾为空表( )。 (15)设广义表 L=((a,b,c)),则 L 的长度和深度分别为( )。 A.1 和 1 B.1 和 3 C.1 和 2 D.2 和 3 答案:C 解释:广义表的深度是指广义表中展开后所含括号的层数,广义表的长度是指广义表中所含元素的个数。根据定义易知 L 的长度为 1,深度为 2。 1.如果一个对象部分地包含自己,或自己定义自己,则称这个对象是_________的对象。 2.如果一个过程直接或间接地_________自己,则称这个过程是一个递归的过程。 3.如果一个问题的解法是递归的,那么最好使用_________。 4.通常程序在调用另一个程序时,都需要使用一个________来保存被调用程序内分配的局部变量、形式参数的存储空间以及返回地址。 5.主程序第一次调用递归函数被称为外部调用,递归函数自己调用自己被称为内部调用,它们都需要建立_________记录。 6.求解递归问题的步骤是:了解题意是否适合用递归方法来求解;决定递归________;决定可将问题规模缩小的递归部分。 7.当程序调用一个函数时,需要先将当前的运行状态_________,当函数调用返回时,程序能够恢复到原来的运行状态继续运行。 8.如果将递归工作栈的每一层视为一项待处理的事务,则位于________处的递归工作记录是当前急待处理的事务。 9.递归工作栈起到两个作用,其一是将递归调用时的实际参数和返回地址传递给下一层递归;其二是保存_________的形式参数和局部变量。 10.函数内部的局部变量是在进入函数过程后才分配存储空间,在________后就将释放局部变量的内存。 11.迷宫问题是一个回溯控制的问题,可使用__________方法来解决。 12.一般可定义广义表为n (n≥0) 个表元素组成的有限_________。 13.因为广义表的表元素可以是子表,因此广义表的结构不是线性结构,而是_________结构。 14.非空广义表的第一个表元素称为广义表的________。 15.非空广义表的除第一个元素外其他元素组成的表称为广义表的________。 16.广义表A ( (a, b, c), (d, e, f ) ) 的表尾为________。 17.广义表是一种递归的数据结构。原子结点即为递归结构的________,而子表结点则指示下一层广义表的表头结点。 18.广义表具有深度。一般定义广义表的深度为广义表括号的________。 参考答案: 1. 递归 2. 调用 3. 递归 1.凡是递归定义的数据结构都可以用递归算法来实现它的操作。 2.通常递归的算法简单、易懂、容易编写,而且效率也高。 3.递归调用算法与相同功能的非递归算法相比,主要问题在于重复计算太多,而且调用本身需要分配额外的空间和传递数据和控制,所以时间与空间开销都比较大。 4.递归方法和递推方法本质上是一回事,例如求n! 时既可用递推的方法,也可用递归的方法。 5.本质上,递归是把一个不能或不好直接求解的“大问题”转化为一个或几个“小问题”来解决,再把这些“小问题”进一步分解为更小的“小问题”来解决,如此分解,直到每一个“小问题”都可以直接解决(此时分解到递归出口)。 6.用非递归方法实现递归算法时一定要使用递归工作栈。 7.将f = 1 + 1/2 + 1/3+ … + 1/n转化为递归函数时,递归部分为f (n) = f (n-1) + 1/n,递归结束条件为f (1) = 1。 8.有如下一个函数说明: 已知a = 10, b = 4, c = 5,在执行k = f ( f (a+c, b), f (b, c) )后,k的值为3。 9.一个广义表的表头总是一个广义表。 10.一个广义表的表尾总是一个广义表。 11.一个广义表 ( (a), ( (b), c), ( ( (d) ) ) ) 的长度为3,深度为4,表尾是 ( (b), c), ( ( (d) ) )。 12.因为广义表有原子结点和子表结点之分,若把原子结点当作叶结点,子表结点当作分支结点,可以借助二叉树的前序遍历算法对广义表进行遍历。 参考答案: 1. 是 2. 否 3. 是 4. 否 5. 是2、填空题
4. 栈 5. 递归工作 6. 结束条件
7. 保存 8. 栈顶 9. 本层
10. 结束函数运行 11. 递归 12. 序列
11. 有序树 14. 表头 15. 表尾
12. ( (d, e, f ) ) 17. 结束 18. 重数3、判断题
int func ( int x, int y ) {
f = x % y + 1;
}
6. 否 7. 是 8. 否 9. 否 10. 是
7. 否 12. 否