字符串

相关概念

  • 串:又称字符串,是由零个或多个字符组成的有限序列。字符串通常用双引号括起来,例如S=“abcdef”,S为字符串的名字,双引号里面的内容为字符串的值。
  • 串长:串中字符的个数,例如S串长为6
  • 空串:0个字符的串,串长为0
  • 子串:串中任意个连续的字符组成的子序列,称为该串的子串,原串称为子串的主串。例如T=“cde”,T是S的子串。子串在主串中的位置,用子串的第一个字符在主串中出现的位置表示。T在S中的位置为3

  • 空格:也算一个字符,例如X="abc  fg" X的串长为6
  • 空格串:全部由空格组成的串称为空格串,空格串不是空串

顺序存储

顺序存储是用一段连续的空间存储字符串。可以预先分配一个固定长度Maxsize的空间,在这个空间中存储字符串。

三种方式

  • 用'\0'表示字符串结束,'\0'不算在字符串长度内。这样做有一个问题:如果想知道串的长度,需要从头到尾遍历一遍,如果经常需要用到串的长度,每次遍历一遍复杂性较高,因此可以考虑将字符串的长度存储起来以便使用。
  • 在0空间存储字符串的长度
  • 结构体变量存储字符串长度(静态分配,容易超过最大长度,出现溢出)

链式存储

单链表存储字符串时,虽然插入和删除非常容易,但是这样做也有一个问题:一个节点只存储一个字符,如果需要存储的字符特别多,会浪费很多空间。因此也可以考虑一个节点存储多个字符的形式,例如一个节点存储3个字符,最后一个节点不够3个时用#代替,如图

但是这样做也有一个大问题:如在第2个字符之前插入一个元素,就需要将b和c后移,那么这种后移还要跨到第二个节点,如同“蝴蝶效应”,一直波及最后一个节点,麻烦就大了!

因此字符串很少使用链式存储结构,还是使用顺序存储结构更灵活一些。

 

模式匹配BF算法

模式匹配:子串的定位运算称为串的模式匹配或串匹配。

假设有两个串S,T,设S为主串,T为子串,也称模式。在主串S中查找与模式T相匹配的子串,如果查找成功,返回匹配的子串第一个字符在主串中的位置

最笨的办法就是穷举所有S的所有子串,判断是否与T匹配,该算法称为BF算法

因为串的模式匹配没有插入、合并等操作,不会发生溢出,因此可以采用第2种字符串顺序存储方法,用0空间存储字符串长度。例如,T的顺序存储方式如图

复杂度分析:

#include <iostream>
#include <cstring>
using namespace std;
#define Maxsize 100 
typedef char SString[Maxsize+1];

bool StrAssign(SString &T,char *chars){//生成一个其值等于chars的串T
	int i;
	if(strlen(chars)>Maxsize)return false;
	else {
		T[0]=strlen(chars);
		for(int i=1;i<=T[0];i++){
			T[i]=*(chars+i-1);
			cout <<T[i]<<" ";
		}
		cout <<endl;
		cout <<"length="<<int(T[0])<<endl;
		return true; 
	}
	
}
int Index_BF(SString S,SString T,int pos){//BF算法 
	// 求子串T在主串S中第pos个字符之后第一次出现的位置
	//其中,T非空,1≤pos≤s[0],s[0]存放S串的长度
	int i=pos,j=1,sum=0;
	while(i<=S[0]&&j<=T[0]){
		sum++;
		if(S[i]==T[j]){//如果相等,则继续比较后面的字符 
			i++;j++; 
		}else{
			i=i-j+2;//i回退到上一轮开始比较下一个字符 
			j=1;//j回退到第一个字符 
		}
	} 
	cout <<"比较的次数是:"<<sum<<endl;
	if(j>T[0])return i-T[0];
	else return 0; 
}
int main(){
	SString  S,T;
	char str[100];
	cout <<"串S:"<<endl;
	cin >>str;
	StrAssign(S,str);
    cout<<"串T:"<<"  ";
    cin>>str;//aaaab
	StrAssign(T,str);
	cout <<Index_BF(S,T,1);
} 

模式匹配KMP算法

BF的改进

直接移动模式串,使前缀移到了后缀的位置


其实i不用回退,让j回退到第3个位置,接着比较即可,如图

因为T串中开头的两个字符和i指向的字符前面的两个字符一模一样,如图。这样j就可以回退到第3个位置继续比较了,因为前面两个字符已经相等了

假设T中当前j指向的字符前面的所有字符为T',只需要比较T′的前缀和T'的后缀即可,如图

前缀是从前向后取若干个字符,后缀是从后向前取若干个字符。注意:前缀和后缀不可以取字符串本身。如果串的长度为n,前缀和后缀长度最多达到n-1

 

动态规划

那么next[j+1]=?

分为以下两种情况:

  • t[k]=t[j],那么next[j+1]=k+1,即相等前缀和后缀的长度比next[j]多1

  • 如果t[k]与t[j]不相等,则回退向前找;找到,相等,则next[j+1]=k'+1;都不相等,直到找到next[1]=0停止

void get_next(SString T, int next[]) //求模式串T的next函数值
{ 
    int j=1,k=0;
    next[1]=0;
    while(j<T[0])   // T[0]为模式串T的长度
        if(k==0||T[j]==T[k])
           next[++j]=++k;
        else
           k=next[k];
}
int Index_KMP(SString S, SString T, int pos, int next[])
{   //利用模式串T的next函数求T在主串S中第pos个字符之后的位置
    //其中,T非空,1\leqslantpos\leqslantS[0],S[0]为模式串S的长度
    int i=pos,j=1;
    while(i<=S[0]&&j<=T[0])
    {
        if(j==0||S[i]==T[j]) // 继续比较后面的字符
        {
            i++;
            j++;
        }
        else
            j=next[j]; // 模式串向右移动
    }
    if(j>T[0]) // 匹配成功
       return i-T[0];
    else
       return 0;
}

改进的KMP算法

void get_next2(SString T, int next[]) //求模式串T的next函数值
{ 
    int j=1,k=0;
    next[1]=0;
    while(j<T[0])   // T[0]模式串T的长度
    {
        if(k==0||T[j]==T[k])
        {
            j++;
            k++;
            if(T[j]==T[k])
                next[j]=next[k];
            else
                next[j]=k;
        }
        else
            k=next[k];
}

 

字符串的应用——病毒检测

题目:疫情暴发,专家发现了一种新型环状病毒,这种病毒的DNA序列是环状的,而人类的DNA序列是线性的。专家把人类和病毒的DNA表示为字母组成的字符串序列,如果在某个患者的DNA中发现这种环状病毒,说明该患者已被感染病毒,否则没有感染。

例如:病毒的DNA为“aabb”,患者的DNA为“ecab”,说明该患者已被感染。因为病毒是环状的,因此“abba”也是该病毒序列,它在患者的DNA中出现了。

解题思路:

该问题属于字符串的模式匹配问题,可以使用前面讲的BF或KMP算法求解。这里需要对环状病毒进行处理,然后调用模式匹配算法即可

环形处理

1)从0下标取4个字符:aabb

2)从1下标取4个字符:abba

3)从2下标取4个字符:bbaa

4)从3下标取4个字符:baab

这4个序列都是病毒序列的变种

线性处理

将该病毒序列扩大两倍,如图。从每个下标(1、2、3、4)开始取4个字符,分别为aabb、abba、bbaa、baab,这4个序列都是病毒序列的变种

运算

依次把每一个环状病毒变种作为子串,把患者DNA序列作为主串,进行模式匹配。一旦匹配成功,立即结束,返回已感染病毒。

bool Virus_detection(SString S, SString T)//病毒检测
{
    int i,j;
    SString temp;//temp记录病毒变种
    for(i=T[0]+1,j=1;j<=T[0];i++,j++)//将T串扩大一倍,T[0]为病毒长度
        T[i]=T[j];
    for(i=0;i<T[0];i++)//依次检测T[0]个病毒变种
    {
        temp[0]=T[0];病毒变种长度为T[0]
        for(j=1;j<=T[0];j++)//取出一个病毒变种
            temp[j]=T[i+j];
        if(Index_KMP(S,temp,1))//检测到病毒
            return 1;
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值