王道数据结构(串4)

Posted 晨沉宸辰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了王道数据结构(串4)相关的知识,希望对你有一定的参考价值。

一、串是什么?

  1. 字符串 string 通常是字符序列,要么是文字常量,要么是某种变量。后者可能允许其元素发生突变,长度改变,或者是固定的(在创建之后)。
  2. 字符串通常被认为是一种数据类型,通常作为字节(或字)的数组数据结构来实现,它存储了一系列元素,通常是使用一些字符编码的字符。
  3. 字符串还可以用来表示更一般的数组或其他序列(或列表)数据类型和结构。
  4. 根据所使用的编程语言和精确的数据类型,声明为字符串的变量可能会导致内存中的存储被静态地分配给预先确定的最大长度,或者可以使用动态分配来容纳可变数量的元素。
  5. 当一个字符串在源代码中出现时,它被称为字符串文字或匿名字符串。
  6. 在数学逻辑和理论计算机科学中使用的正式语言中,string 是由一个叫做字母表的集合所选择的有限的符号序列。
  7. 串的实现形式有两种,堆分配和块链存储,本文讲述块链存储
  8. 串是由零个或多个字符组成的有限序列 一般记为: S = ‘ a 1 a 2 a 3 … a n ’ S=‘a1a2a3…an’ S=a1a2a3an其中S是串名,双引号括起来的就是串值,ai可以是数字字母或者其他字符,字符个数n是串的长度(包括空格),n=0时(长度为0)的串为空串,构成串的所有字符都是空格的串称为空格串,特别的,空串是任意串的子串,任意串是自身的子串。
  9. 串中任意多个连续的字符组成的子序列称为该串的子串,包括子串的串称为主串
  10. 子串在主串的位置就是子串第一个字符在主串中的位置来表示
  11. 当两个串的长度相同、对应字符都相同,那么这两个串相同
  12. 串的位序从1开始
  13. 串常量与整常数、实常数一样,在程序中只能被引用但不能不能改变其值,即只能读不能写,通常串常量是有直接量来表示的,例如语句错误(“溢出”)中“溢出”就是直接量、串变量是可以改变的
  14. 串的逻辑结构与线性表很是相似,区别在于串的数据对象限定为字符集
  15. 线性表的基本操作是以单个元素作为操作对象,而串是以字串作为操作对象

二、基本操作

1. 概述

假设有串 T = ‘’, S = ‘iPhone 11 Pro Max?’, W = ‘Pro’
(1)StrAssign(&T, chars): 赋值操作,把串T赋值为chars;
(2)StrCopy(&T, S): 复制操作,把串S复制得到串T
(3)StrEmpty(S): 判空操作,若S为空串,则返回TRUE,否则返回False;
(4)StrLength(S): 求串长,返回串S的元素个数;
(5)ClearString(&S): 清空操作,将S清为空串;
(6)DestroyString(&S): 销毁串,将串S销毁——回收存储空间;
(7)Concat(&T, S1, S2): 串联联接,用T返回由S1和S2联接而成的新串———可能会导致存储空间的扩展;
(8)SubString(&Sub, S, pos, len): 求子串,用Sub返回串S的第pos个字符起长度为len的子串;
(9)Index(S, T): 定位操作,若主串S中存在与串T值相同的子串,则返回它再主串S中第一次出现的位置,否则函数值为0;
(10)StrCompare(S, T): 串的比较操作,参照英文词典排序方式;若S > T,返回值>0; S = T,返回值=0 (需要两个串完全相同) ; S < T,返回值<0;

2.串的实现

2.1 定长顺序存储表示

(1)代码实现

串定义成字符数组,利用串名可以直接访问串值,用这种表示方式,串的存储空间在编译时确定,其大小不能改变

#define MAXLEN 255   //预定义最大串长为255

typedef struct
    char ch[MAXLEN];   //静态数组实现(定长顺序存储)
                       //每个分量存储一个字符
                       //每个char字符占1B
    int length;        //串的实际长度
SString;

(2)串长的表示法

(1)方案一:用一个额外的变量length来存放串的长度(保留ch[0]);
(2)方案二:用ch[0]充当length;
优点:字符的位序和数组下标相同;
(3)方案三:没有length变量,以字符’\\0’表示结尾(对应ASCII码的0);
缺点:需要从头到尾遍历;
(4)方案四——最终使用方案:ch[0]废弃不用,声明int型变量length来存放串的长度(方案一与方案二的结合
基本操作实现(基于方案四)

2.2 堆分配存储表示

(1)堆存储结构的特点

仍以一组空间足够大的、地址连续的存储单元依次存放字符序列,但它们的存储空间实在程序执行过程种动态分配的 。
通常,C语言提供的串类型就是以这种存储方式实现的。由动态分配函数malloc()分配一块实际串长所需要的存储空间(“堆”),如果分配成功,则返回此空间的起始地址,作为串的基址。由free()释放串不再需要的空间,

(2)堆存储结构的优点

堆存储结构既有顺序存储结构的特点,处理(随机取子串)方便,操作中对串长又没有任何限制,更显灵活,因此在串处理的应用程序中常被采用。

(3)代码实现

//动态数组实现
typedef struct
    char *ch;           //按串长分配存储区,ch指向串的基地址
    int length;         //串的长度
HString;
    HString S;
    S.ch = (char *) malloc(MAXLINE * sizeof(char)); //基地址指针指向连续空间的起始位置
    //malloc()需要手动free()
    S.length;

(4)串的连接操作

bool Hstring *StrConcat(Hstring *T,Hstring *s1,Hstring *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("系统空间不足,申请空间失败")return false;
    
    for(j=0;j<s1->length;j++)
      T->ch[j]=s1->ch[j];
    
      for(k=s1->length,j=0;j<s2->length;j++,k++)
      T->ch[j]=s1->ch[j];
    
    free(s1->ch);
    free(s2->ch);
    return true;

2.3 串的链式存储

一种链式储存结构。

(1)基础实现方法

typedef struct StringNode
    char ch;           //每个结点存1个字符
    struct StringNode *next;
StringNode, * String;

(2)块链结构

上述问题:存储密度低,每个字符1B,每个指针4B;
解决方案:每一个链表的结点存储多个字符——每个结点称为块——块链结构

  1. 块结点的结构定义
typedef struct StringNode
    char ch[4];           //每个结点存多个个字符
    struct StringNode *next;
StringNode, * String;
  1. 块链串的类型定义
typedef struct 
    StringNode head;          //头指针
    int Strlen;
Blstring;
  • 在这种存储结构下,结点的分配总是完整的结点为单位,因此,为使一个串能UC你党在整数个结点中,在串末尾填上不属于串值的特殊字符,表示串的终结
  • 当一个块内存放多个字符时,往往会使操作过程变得更加复杂,如在串中插入和删除字符操作时通常需要在块间移动字符

(3)结合链表思考优缺点

  • 存储分配角度:链式存储的字符串无需占用连续空间,存储空间分配更灵活;
  • 操作角度:若要在字符串中插入或删除某些字符,则顺序存储方式需要移动大量字符,而链式存储不用;
  • 若要按位序查找字符,则顺序存储支持随机访问,而链式存储只支持顺序访问;

3.串的基本操作

3.1 初始化串

int StrAssign(BBT &T,char *chars)
	int i ;
	char *c;
//	if(T.ch)
//		free(T.ch);
	for(i=0,c=chars;*c;i++,c++);
	if(!i)
		T.ch=NULL;
		T.length=0;
	//如果所求的串是空串 
	else
		if(!(T.ch=(char*)malloc(i*sizeof(char))))
			return -1;
		
		for(int n=0;n<=i-1;n++)
			T.ch [n]=chars[n];
		T.length =i;
		
	return 0; 

3.2 求子串的功能实现

SubString(&Sub,S,pos,len):求子串,用Sub返回串S的第pos个字符起长度为len的子串

bool SubString(SString &Sub, SString S, int pos, int len)
    //子串范围越界
    if (pos+len-1 > S.length)// 从1开始为下标,注意需要-1
        return false;
    for (int i=pos; i<pos+len; i++)// 注意是<而非≤
        Sub.cn[i-pos+1] = S.ch[i];//储存这个子串,序号从1开始
    Sub.length = len;
    return true;

3.3 两串比较的功能实现

StrCompare( S, T):比较操作,若S>T返回值>0;反之,相等返回0,S<T 返回<0

int StrCompare(SString S, SString T)
    for (int i; i<S.length && i<T.length; i++)
        if(S.ch[i] != T.ch[i])
            return S.ch[i] - T.ch[i];
    
    //扫描过的所有字符都相同,则长度长的串更大
    return S.length - T.length;

3.4 定位功能

Index(S, T):定位操作,如果主串s存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值为0

int Index(SString S, SString T)
    int i=1;
    n = StrLength(S);
    m = StrLength(T);
    SString sub;        //用于暂存子串
    while(i<=n-m+1) //只需要比较n-m-1次,需要思考
        SubString(Sub,S,i,m); //取出从位置 i开始,长度为m的子串
        if(StrCompare(Sub,T)!=0)
            ++i;
        else 
            return i;    // 返回子串在主串中的位置
    
    return 0;            //S中不存在与T相等的子串


3.5 求串的长度功能

int StrLength(SString S)
	return S.length;
 

3.6 串的输出功能

void StrPrint(SStringg T)

	int i;
	for(i=0;i<T.length;i++)
	
		printf("%c",T.ch[i]);
	
 

3.7 判断串的是否为空功能

bool StrClear(SString &S)

	if(S.ch)
	    free(S.ch);
		S.ch=NULL;
	
	S.length=0;
	return true;

3.8 串的连接操作

bool StrConcat(SString s,SString t)
    int i,j;
    if((s.length+t.length)>MAXLEN)
        return false;
    for(i-0;i<t.length;i++)
        s.ch[s.length+1]=t.ch[i];
        s.length=s.length+t.length;
    
    return true;

三、 模式匹配法

模式匹配:子串的定位操作称为串的模式,它求的是子串(常称模式串)在主串中的位置。

1. 朴素模式匹配算法(Brute-Force)

(1) 思想:找到主串中所有长度为m的子串与模式串进行对比,实现暴力匹配,直到找到一个完全匹配的子串或所有子串都不匹配为止。
① 设置两个指示ij分别指向主串和模式串
② 比较当前指示字符,相同则ij后移,表示目前匹配成功
③如果不相同,则匹配失败,则主串指针i指向下一个子串的第一个位置,模式串指针j回到模式串的第一个位置
④ 如果模式串j>模式串长度,那么说明子串匹配成功,返回当前子串的第一个字符的位置 i - 模式串长度
(2)代码实现

int Index(SString S, SString T)
    int i=1;                //扫描主串S
    int j=1;                //扫描模式串T
    while(i<=S.length && j<=T.length)
        if(S.ch[i] == T.ch[j]) //如果当前相同,则指示都后移
            ++i;
            ++j;             //继续比较后继字符
        
        else       
            i = i-j+2;   //i-j表示指向了当前匹配串的起始位置的前一个,+2表示下一个串的起始位置(+1是当前被匹配串的起始位置)
            j=1;             //指针后退重新开始匹配
        
    
    if(j>T.length)
        return i-T.length;
    else
        return 0;

(3)总结

  • 最多匹配n-m+1次
  • 其实与上面的定位操作类似
  • 时间复杂度:主串长度n,模式串m【实际操作过程中,时间复杂度接近于O(n+m)】
    最坏时间复杂度 = O(nm)
    每个子串都要对比m个字符(对比到最后一个字符才匹配不上),共要对比n-m+1个子串,复杂度 = O ( ( n − m + 1 ) m ) = O ( n m − m 2 + m ) = O ( n m ) O((n-m+1)m) = O(nm - m^2 + m) = O(nm) O((nm+1)m)=O(nmm2+m)=O(nm)
    PS:大多数时候,n>>m,所以删去的是 m 2 m^2 m2以及m
    最好时间复杂度 = O(n)
    每个子串的第一个字符就匹配失败,共要对比n-m+1个子串,复杂度 = O(n-m+1) = O(n)

2. KMP算法

  1. 改进在于:每当一趟匹配过程出现字符不相等时,主串指示不用回溯,
    而是利用已经得到的“部分匹配”结果,将模式串的指示器向右“滑动”尽可能远的一段距离后,继续进行比较
  2. 总之,在主串s与模式串t的匹配过程中,一旦出现si ≠ tj, 主串s的指针不必回溯,而是直接与模式串的 tk (0≤k<j) 进行比较,而k的取值与主串s无关,只与模式串t本身的构成有关,即从模式串t可求得k值。

(1) 求出next数组

  1. 规律:只看模式串,当前位置前面的子串最大公共位数+1(第一二位除外,第一位next[1]=0,第二位next[2]==1)
  2. 例子 :ababaaababaa
    ① 第一位next[1]=0,第二位next[2]==1
ababaaababaa
01

② 判断第三位前最大公共子串位数+1 ab最大为0 ,+1=1

ababaaababaa
011

③ 判断第四位前最大公共子串位数+1 aba最大为1,+1=2

ababaaababaa
0112

④ 判断第五位前最大公共子串位数+1 abab最大为2,+1=3

ababaaababaa
01123

⑤ 判断第六位前最大公共子串位数+1 ababa最大为3(aba与aba),+1=4

ababaaababaa
011234

⑥ 判断第七位前最大公共子串位数+1 ababaa最大为1(a与a),+1=2

ababaaababaa
0112342

⑥ 最终结果

ababaaababaa
011234223456
  1. 代码:
void next(SString t,int next[])
    int k=1,j=0;
    next[1]=0;
    while(k<t.length)
        if((j==0)||(t.str[k]==t.str[j]))
            k++;j++;
            if(t.ch[k]==t.ch[j])
                next[k]=j;
            
            elsenext[j]=j;
        
     

(2)进行KMP算法

1. 思想
① 设目标串(主串)为 s s s,模式串为 t t t,并设i指针和j指针分别指示目标串和模式串中正待比较的字符,设i和j的初值均为1。
② 若有 s = t s=t s=t,则 i i i j j j分别加1。否则, i i i不变,j退回到 j = n e x t [ j ] j=next[j] j=next[j]的位置,
③ 再比较si 和 tj,若相等,则 i i i j j j分别加1。否则, i i i不变, j j j再次退回到 j = n e x t [ j ] j=next[j] j=next[j]的位置,依此类推。
④ 直到下列两种可能:
(1) j j j退回到某个下一个 [ j ] [j] [j]值时字符比较相等,则指针各自加1继续进行匹配。
(2)退回到j=0,将 i i i j j j分 别加1,即从主串的下一个字符 si+1 模式串的 t1 重新开始匹配。
2. 代码

int Index_KMP(SString S, SString T, int next[])
    int i=1;     //主串
    int j=以上是关于王道数据结构(串4)的主要内容,如果未能解决你的问题,请参考以下文章

王道数据结构与算法串

数据结构-王道2017-第4章 树与二叉树-树森林

(王道408考研数据结构)第四章串-第一节:串的定义和基本操作及存储结构

(王道408考研数据结构)第四章串-第二节:串的模式匹配算法(朴素和KMP)

数位拆解(王道)

《编译原理》4