数据结构--第4~6章--串、数组、广义表--知识点回顾

第4~6章 串、数组、广义表

一、基本知识点

(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 <iostream>
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;
	}
}

链串不常用

BF算法

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);        		//模式匹配不成功
}


KMP算法

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);        		//返回不匹配标志
}

改进的KMP算法

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.选择题

(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<j)的位置 k 的关系为( )。

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。

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. 递归
4. 栈 5. 递归工作 6. 结束条件
7. 保存 8. 栈顶 9. 本层
10. 结束函数运行 11. 递归 12. 序列
11. 有序树 14. 表头 15. 表尾
12. ( (d, e, f ) ) 17. 结束 18. 重数

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.有如下一个函数说明:

int func ( int x, int y ) {
  f = x % y + 1;
} 

已知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. 是
6. 否 7. 是 8. 否 9. 否 10. 是
7. 否 12. 否

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值