数据结构期末复习(第四章 串)

数据结构期末复习(第四章 串)

很简单的一个单元,类似过渡单元(?) 唯一重难点好像就是kmp算法了(雾)

Part 1、知识点总结

在非数值处理、事务处理等问题常涉及到一系列的字符操作。计算机的硬件结构主要是反映数值计算的要求,因此,字符串的处理比具体数值处理复杂。本章讨论串的存储结构及几种基本的处理。

1.1 串的基本概念

串(字符串):是零个或多个字符组成的有限序列。记作: S=“a1a2a3…”,其中S是串名,ai(1≦i≦n)是单个,可以是字母、数字或其它字符。
串值:双引号括起来的字符序列是串值。
串长:串中所包含的字符个数称为该串的长度。
空串(空的字符串):长度为零的串称为空串,它不包含任何字符。
空格串(空白串):构成串的所有字符都是空格的串称为空白串。
注意:空串和空白串的不同,例如“ ”和“”分别表示长度为1的空白串和长度为0的空串。
子串(substring):串中任意个连续字符组成的子序列称为该串的子串,包含子串的串相应地称为主串。
子串的序号:将子串在主串中首次出现时的该子串的首字符对应在主串中的序号,称为子串在主串中的序号(或位置)。
例如,设有串A和B分别是:
A=“这是字符串”,B=“是”
则B是A的子串,A为主串。B在A中出现了两次,其中首次出现所对应的主串位置是3。因此,称B在A中的序号为3 。
特别地,空串是任意串的子串,任意串是其自身的子串。
串相等:如果两个串的串值相等(相同),称这两个串相等。换言之,只有当两个串的长度相等,且各个对应位置的字符都相同时才相等。
通常在程序中使用的串可分为两种:串变量和串常量。
串常量和整常数、实常数一样,在程序中只能被引用但不能不能改变其值,即只能读不能写。通常串常量是由直接量来表示的,例如语句错误(“溢出”)中“溢出”是直接量。
串变量和其它类型的变量一样,其值是可以改变。

1.2 串的存储表示和实现

串是一种特殊的线性表,其存储表示和线性表类似,但又不完全相同。串的存储方式取决于将要对串所进行的操作。串在计算机中有3种表示方式:

◆ 定长顺序存储表示:将串定义成字符数组,利用串名可以直接访问串值。用这种表示方式,串的存储空间在编译时确定,其大小不能改变。
◆ 堆分配存储方式:仍然用一组地址连续的存储单元来依次存储串中的字符序列,但串的存储空间是在程序运行时根据串的实际长度动态分配的。
◆ 块链存储方式:是一种链式存储结构表示。

1.2.1 串的定长顺序存储表示

这种存储结构又称为串的顺序存储结构。是用一组连续的存储单元来存放串中的字符序列。所谓定长顺序存储结构,是直接使用定长的字符数组来定义,数组的上界预先确定。
定长顺序存储结构定义为:

#define MAX_STRLEN  256
typedef  struct
{  char  str[MAX_STRLEN] ;
int  length;
} StringType ;  

1 串的联结操作

Status  StrConcat ( StringType  s, StringType t)
/*  将串t联结到串s之后,结果仍然保存在s中  */
{  int i,  j ;
if ((s.length+t.length)>MAX_STRLEN)
Return ERROR ;   /*  联结后长度超出范围  */
 for (i=0 ; i<t.length ; i++)
s.str[s.length+i]=t.str[i] ;   /*  串t联结到串s之后  */
s.length=s.length+t.length ;  /* 修改联结后的串长度 */
return OK ;
}

2 求子串操作

Status SubString (StringType s, int pos, int len, StringType *sub)
{  int k,  j ;
if (pos<1||pos>s.length||len<0||len>(s.length-pos+1))
return ERROR ;   /*  参数非法  */
sub->length=len-pos+1 ;   /*  求得子串长度  */
for (j=0, k=pos ; k<=leng ; k++, j++)
sub->str[j]=s.str[i] ;   /*  逐个字符复制求得子串  */
return OK ;
}

1.2.2 串的堆分配存储表示

实现方法:系统提供一个空间足够大且地址连续的存储空间(称为“堆”)供串使用。可使用C语言的动态存储分配函数malloc()和free()来管理。
特点是:仍然以一组地址连续的存储空间来存储字符串值,但其所需的存储空间是在程序执行过程中动态分配,故是动态的,变长的。
串的堆式存储结构的类型定义

typedef  struct
{  char *ch;    /*  若非空,按长度分配,否则为NULL */
int length;      /*  串的长度  */
} HString ;

1 串的联结操作

Status  Hstring  *StrConcat(HString  *T, HString *s1, HString *s2)
/*  用T返回由s1和s2联结而成的串  */  
{   int k,  j , t_len ; 
if (T.ch)  free(T);     /*  释放旧空间   */
t_len=s1->length+s2->length ;
if ((p=(char *)malloc(sizeof((char)*t_len))==NULL)
{   printf(“系统空间不够,申请空间失败 !\n”) ; 
return ERROR  ;     }
for (j=0 ; j<s->length; j++) 
T->ch[j]=s1->ch[j] ;    /*  将串s复制到串T中 */
for (k=s1->length, j=0 ; j<s2->length; k++, j++) 
T->ch[j]=s1->ch[j] ;    /*  将串s2复制到串T中 */
free(s1->ch) ; 
free(s2->ch) ; 
return OK ;   
}

1.2.3 串的链式存储表示

串的链式存储结构和线性表的串的链式存储结构类似,采用单链表来存储串,结点的构成是:
◆ data域:存放字符,data域可存放的字符个数称为结点的大小;
◆ next域:存放指向下一结点的指针。
若每个结点仅存放一个字符,则结点的指针域就非常多,造成系统空间浪费,为节省存储空间,考虑串结构的特殊性,使每个结点存放若干个字符,这种结构称为块链结构。如图4-1是块大小为3的串的块链式存储结构示意图。
在这里插入图片描述
⑴ 块结点的类型定义

#define BLOCK_SIZE  4
typedef  struct  Blstrtype
{  char  data[BLOCK_SIZE] ;
struct  Blstrtype  *next;
}BNODE ;

(2) 块链串的类型定义

typedef  struct
  {  BNODE  head;     /*  头指针  */
      int  Strlen ;        /*  当前长度  */
   } Blstring ;

· 在这种存储结构下,结点的分配总是完整的结点为单位,因此,为使一个串能存放在整数个结点中,在串的末尾填上不属于串值的特殊字符,以表示串的终结。
当一个块(结点)内存放多个字符时,往往会使操作过程变得较为复杂,如在串中插入或删除字符操作时通常需要在块间移动字符。

1.3 串的模式匹配算法

模式匹配(模范匹配):子串在主串中的定位称为模式匹配或串匹配(字符串匹配) 。模式匹配成功是指在主串S中能够找到模式串T,否则,称模式串T在主串S中不存在。
模式匹配的应用在非常广泛。例如,在文本编辑程序中,我们经常要查找某一特定单词在文本中出现的位置。显然,解此问题的有效算法能极大地提高文本编辑程序的响应性能。
模式匹配是一个较为复杂的串操作过程。迄今为止,人们对串的模式匹配提出了许多思想和效率各不相同的计算机算法。介绍两种主要的模式匹配算法。

1.3.1 Brute-Force模式匹配算法

    设S为目标串,T为模式串,且不妨设:

S=“s0s1s2…sn-1” , T=“t0t1t2 …tm-1”
串的匹配实际上是对合法的位置0≦i≦n-m依次将目标串中的子串s[i…i+m-1]和模式串t[0…m-1]进行比较:
◆ 若s[i…i+m-1]=t[0…m-1]:则称从位置i开始的匹配成功,亦称模式t在目标s中出现;
◆ 若s[i…i+m-1]≠t[0…m-1]:从i开始的匹配失败。位置i称为位移,当s[i…i+m-1]=t[0…m-1]时,i称为有效位移;当s[i…i+m-1] ≠t[0…m-1]时,i称为无效位移。
这样,串匹配问题可简化为找出某给定模式T在给定目标串S中首次出现的有效位移。
算法实现:

int IndexString(StringType  s , StringType  t , int pos )
   /*   采用顺序存储方式存储主串s和模式t,   */
   /*   若模式t在主串s中从第pos位置开始有匹配的子串,*/
   /*   返回位置,否则返回-1   */
{  char *p , *q ;
int  k , j ;
k=pos-1 ; j=0 ; p=s.str+pos-1 ; q=t.str ;   
/*  初始匹配位置设置  */
 /*  顺序存放时第pos位置的下标值为pos-1 */
 while (k<s.length)&&(j<t.length)
{  if (*p==*q)   { p++ ; q++ ; k++ ; j++ ; } 
else { k=k-j+1 ; j=0 ; q=t.str ; p=s.str+k ;  }
/*  重新设置匹配位置    */
}
if (j==t.length)
     return(k-t.length) ;   / *   匹配,返回位置   */
else return(-1) ;      /*   不匹配,返回-1   */
}

该算法简单,易于理解。在一些场合的应用里,如文字处理中的文本编辑,其效率较高。
该算法的时间复杂度为O(n*m) ,其中n 、m分别是主串和模式串的长度。通常情况下,实际运行过程中,该算法的执行时间近似于O(n+m) 。
理解该算法的关键点
当第一次sk≠tj时:主串要退回到k-j+1的位置,而模式串也要退回到第一个字符(即j=0的位置)。
比较出现sk≠tj时:则应该有sk-1=tj-1,…,sk-j+1=t1, sk-j=t0 。

1.3.2 KMP算法

好多人迷在这儿主要是不好讲
大概说一哈
大概就是字符串有前后缀
举个例子:
abcabcab
前缀的集合: a,ab,abc,abca…
后缀的集合:b,ab,cab,bcab…
到这儿应该能搞懂前后缀是啥了,不难叭
然后就是KMP是个啥子东西
它首先需要一个next数组
举个例子:
S:abcabeabcabcmn
T:abcabcmn
所谓next数组,只考虑子串
next[i] = j;
i就是你字母的下标
j就是它前面的串拥有长度为j的完全相同的前缀和后缀
对于T:

next[0]next[1]next[2]next[3]next[4]next[5]next[6]next[7]
-10001230

特殊的,规定对于第一个字符,前面没有串,所以next[0] = -1;
分析过程就是
对于i = 1
前面的串是a
前后缀都是空,j=0
i = 2
前面的串是ab
没有相同的前后缀吧,因此j = 0;
i = 3
前面的串是abc
没有相同的前后缀吧,因此j = 0;

i = 6
前面的串是abcabc
j = 3 时,前后缀最长且相等
大概就是这么个分析过程
到这儿能理解,基本上就没啥子问题了
接下来就是一个循环的事儿
首先S和T的开头对其,挨个往后比,到下标为5的地方,哎嘿不一样了
接下来就把T[next[5]]的元素对齐S[5],继续往后比
一直循环这个过程就ok
在这里插入图片描述

PPT课后题

⑴ 解释下列每对术语的区别:空串和空白串;主串和子串;目标串和模式串。
⑵ 若x和y是两个采用顺序结构存储的串,写一算法比较这两个字符串是否相等。
⑶ 写一算法void StrRelace(char *T, char *P, char *S),将T中第一次出现的与P相等的子串替换为S,串S和P的长度不一定相等,并分析时间复杂度。

自己康的一点题

  • 串长度是指串所含的字符数目

Part 2、代码

//kmp
#include <stdio.h>
#define max 100
#define error -2
typedef char Elemtype;
typedef struct{
	int length;
	Elemtype data[max];
}SqString;
SqString create(SqString t)
{
	scanf("%d",&t.length);
	getchar();
	for(int i = 0;i < t.length;i++)
	{
		scanf("%c",&t.data[i]);
	}
	getchar();
	return t;
}
void getnext(SqString t,int next[]){
	int j = 0,k = -1;
	next[0] = -1;
	while(j < t.length)
	{
		if(k == -1 || t.data[j] == t.data[k])
		{
			j++;
			k++;
			next[j] = k;
		}
		else
		{
			k = next[k];
		}
	}
}
int KMP(SqString s,SqString t,int next[])
{
	int i = 0,j = 0;
	while(i < s.length && j < t.length)
	{
		if(j == -1 || s.data[i] == t.data[j])
		{
			i++;
			j++;
		}
		else
			j = next[j];
	}
	if(j >= t.length)
		return (i-t.length);
	else
		return error;
}
void print(SqString t)
{
	for(int i = 0;i < t.length;i++)
	{
		printf("%c",t.data[i]);
	}
}
int main() {
	SqString s,t;
	printf("请输入主串长度以及data:\n");
	s = create(s);
	printf("请输入子串长度以及data:\n");
	t = create(t);
	int next[max];
	getnext(t,next);
	printf("next数组为:\n");
	for(int i = 0;i < t.length;i++)
		printf("%d ",next[i]);
	printf("\n");
	int tmp = KMP(s,t,next);
	if(tmp == error)
		printf("匹配失败!");
	else
		printf("在下标%d处匹配",tmp);
	printf("\n");
	printf("主串:\n");
	print(s);
	printf("\n");
	printf("子串:\n");
	print(t);
	return 0;
}

//子串的查找替换
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#define Maxsize 50
#define error -1
#define ok 1
char* StrRelace(char T[],char P[],char S[])
{
	char len1,len2,len3,tmp[100];
	len1=strlen(T);len2=strlen(P);len3=strlen(S);
	int i,j;
	for(i=0;i<len1-len2+1;i++)
	{
		for(j=0;j<len2;j++)
		{
			tmp[j]=T[i+j];
		}
		tmp[j]='\0';
		if(strcmp(P,tmp)==0)
		{
			for(j=i;j<len1;j++)
			tmp[j-i]=T[j];
			tmp[j-i]='\0';
			for(j=i;j<i+len3;j++)
			T[j]=S[j-i];
			for(j=i+len3;j<i+len3+len1;j++)
			T[j]=tmp[j-i-len3+len2];
		}
	}
	puts(T);
}
int main()
{
	char tmp,T[Maxsize],P[Maxsize],S[Maxsize];
	int i;
	i=0;
	printf("Please input string T:\n");
	while(1)
	{
		scanf("%c",&tmp);
		if(tmp!='\n')
		{
			T[i]=tmp;
			i++;
		}
		else{
			T[i]='\0';
			break;
		}
	}
	i=0;
	printf("Please input string P:\n");
	while(1)
	{
		scanf("%c",&tmp);
		if(tmp!='\n')
		{
			P[i]=tmp;
			i++;
		}
		else{
			P[i]='\0';
			break;
		}
	}
	i=0;
	printf("Please input string S:\n");
	while(1)
	{
		scanf("%c",&tmp);
		if(tmp!='\n')
		{
			S[i]=tmp;
			i++;
		}
		else{
			S[i]='\0';
			break;
		}
	}
	StrRelace(T,P,S);
}

Part 3、总结

着重理解KMP算法就ok
无非就是用线性表、链表的形式存储字符
和之前c语言入门时学的数组存储大同小异
只不过换了一种思想来操作,本质没啥变化

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赤赤赤赤赤赤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值