数据结构【第六天】:串(字符串)

由零个或者多个字符组成的有限序列,又名字符串

区分空格串和空字符串

ADT 串(string)

Data

    串中元素仅由一个字符组成,相邻元素具有前驱和后继关系

Operation

    StrAssign(T,*chars);  //生成一个值等于字符串常量chars的串T

    StrCopy(T,S);  //若串S存在,由串S复制得串T

    ClearString(S);  //若串S存在,将串清空

    StringEmpty(S);  //串S为空,返回true,否则返回false

    StrLength(S);    //返回串S的元素个数,即串的长度

    StrCompare(S,T);  //若S>T,返回值>0;若S=T,返回0,若S<T,返回<0

    Concat(T,S1,S2);  //用T返回S1,S2连接而成的新串T

    SubString(Sub,S,pos,len);  //串S存在,且1<= pose <=StrLength(S),0<=len<=StrLength(S)-pose+1

                                               //用Sub返回串S的第pos个字符起长度为len的子串

    Index(S,T,pos);  //若S,T存在,T为非空,且1<= pose <=StrLength(S),若S中存在与T串的值相等的子串,则返回它在                                  //第pos个字符之后第一次出现的位置,否则返回0

    Replace(S,T,V);  //用V替换S中出现的T

    StrInsert(S,pos,T);  //S中第pos个字符后插入T

    StrDelete(S,pos,len);  //删除串S第pos个字符后的长度为len的子串

endADT

Index操作

int Index(String S,String T,int pos)
{
    int n,m,i;
    String sub;
    if(pos>0)
    {
        n = StrLength(S);    //主串长n
        m = StrLength(T);    //子串长m
        i = pos;
        while(i<n-m+1)    //循环n-m+1-pos次
        {
            SubString(sub,S,i,m);    //从pos开始取S后的m个数,赋给sub
            if(StrCompare(sub,T) != 0)
                i++;    //不匹配继续后移
            eles
                return i;    //匹配则返回该处位置i  
        }
    }
    return 0;
}

串的顺序存储结构

规定在最后加入”\0“来表示串值的终结,其占用一个空间,但是不算入串长度

方法一: 使用定长数组存储

方法二:使用malloc()\free() 或者new\delete 来实现动态分配

串的链式存储结构

链式结构相似于线性表,但是一个节点存储一个字符太过浪费空间,所以可以一个节点存储多个字符,若有未满的节点,可用”#“或其他非串值字符补全。

该结构在连接串时有一定优势,但是总的来说不如顺序存储灵活。


朴素模式匹配算法

子串的定位操作通常称为串的模式匹配,是串操作中最重要的操作之一。

朴素的匹配算法即如前面的代码一样,逐个遍历比较,以下为只是用基础数组来实现的方法。

假设S[0]与T[0]中存储了串的长度

int Index(String S,String T,int pos)
{
    int i = pos;    //记录起始位置
    int j = i;
    while(i<=S[0] && j<= T[0])    //i<小于S长度,且j小于T的长度
    {
        if(S[i] == T[j])    //若匹配则一直继续
        {
            ++i;
            ++j;
        }
        else                //若不匹配,则i相比pos之前+1,重新匹配
        {
            i = i-j+2;
            j = 1;            //j回退到T首位
        }
    }
    if(j > T[0])            //如果j大于T的长度,即完全匹配
        return i-T[0];      //返回在S中的位置
    else
        return 0;           //若是超过S的长度,则返回0
}

缺点在于效率低,复杂度为O{(n-m+1)*m}

KMP模式匹配算法

当子串T中具有大量重复的字符,如abcdabcde时,可以使用该算法来简化匹配算法

将T串各个位置的j值的变化定义为一个数组next,其next的长度为T的长度

next[j]的值(也就是k)表示,当T[j] != P[i]时,j指针的下一步移动位置

j=1时 next[1] = 0;

Max{k|1<k<j,且'p1...pk-1' = 'pj-k+1...pj-1'} next[j] = Max

其他情况 next[j] = 1;

以下为一些例子

例1:

j 123456
模式串T abcdex
next[j] 011111

例2

j 123456
模式串T abcabx
next[j] 011123

j为5时 1到j-1的串为abca p1 = p4 个数为1 所以next[j] = 2

j为6时 1到j-1的串为abcab p1p2 = p4p5 个数为2 所以next[j] = 3

例3

j 123456
模式串T ababaaaba
next[j] 011234223

j为4时 串为aba next[j] = 2

j为5时 串为abab next[j] = 3

j为6时 串为ababa  p1p2p3 = p3p4p5 所以next[j] = 4

j为7时 串为ababaa p1=p6 next[j] = 2

由上述的规则可以获得数组next的获得代码如下

void get_next(String T,int *next)    //计算T的next数组
{
    int i,j;
    i = 1;
    j = 0;
    next[1] = 0;     //初始化  
    while(i<T[0])    //此处T[0]为串T的大小
    {
        if(j == 0||T[i] == T[j] )    //T[i]代表后缀的单个字符  T[j]代表前缀的单个字符 
        {
            ++i;            //i = 2 
            ++j;            //j = 1
            next[i] = j;    //next[2] = 1;
        }
        else
            j =next[j];    //若字符不同,则j值回溯
    }
}

ababc
i = 1 j = 0 next[1] =0;
i = 2 j = 1 next[2] =1;
j = next[1] = 0;
i = 3;j = 1; next[3] = 1;
T[3] = T[1]; i=4 j=2 next[4] = 2;
T[4] = T[2]; i=5 j=3 next[5] = 3;
T[5] != T[3]; j=next[3] = 1;
T[5] != T[1]; j=next[1] = 0;
i=6;j=1;next[6] = 1;

当T[i] = T[j]时,有next[j+1] == next[j] + 1

当T[i]  != T[j]时,有j = next[j];

上述代码不太好理解,抽象地说来,就是初始化next[1]为0;然后当j为0或者有字符相等时,i与j均++,然后计算next的值即next[i] = j;而如果字符继续相等,则next的下一个值会继续在j的基础上加下去,而i则不断推进。一旦字符不相等了,j会随着next[j]的值进行回溯,直至相等或者回退到0,然后重新开始匹配。 

有了next指针,kmp算法如下

int Index_KMP(String S,String T,int pos)
{
    int i = pos;    //记录起始位置
    int j = 1;      //修改1  
    int next[255];
    get_next(T,next);    //获得next数组
    while(i<=S[0] && j<= T[0])    //i<小于S长度,且j小于T的长度
    {
        if(j==0||S[i] == T[j])    //修改2 若匹配或j回溯到0则一直继续
        {
            ++i;
            ++j;
        }
        else                //若不匹配,则i相比pos之前+1,重新匹配
        {
            j = next[j];       //修改3 j回退到T首位
        }
    }
    if(j > T[0])            //如果j大于T的长度,即完全匹配
        return i-T[0];      //返回在S中的位置
    else
        return 0;           //若是超过S的长度,则返回0
}

其相比朴素匹配算法仅修改了3处并增加了next数组,相比而言优势在于其i值没有进行回溯。复杂度为O(n+m)

KMP模式匹配算法改进

缺陷:假设主串为aaaadef 模式串为aaaabcd则在i=5,j=5时,发现不相等 于是j值需要依次回溯从4直到1;最终在i=6,j=1时终结;可以发现这个过程是多余的。

改进:当前4个字符均相等时,可以使用首位的next[1]的值代替与他相等的字符后续的值,改进后的数组为nextval

j 123456789
模式串T ababaaaba
next[j] 011234223
nextval[j] 010104210
j 123456789
模式串T aaaaaaaab
next[j] 012345678
nextval[j] 000000008

计算过程即为判断第j个字符,与第next[j]个字符是否相等,相等则nextval[j] =nextval[next[j]] ,否则nextval[j] = next[j];

求nextval程序如下

void get_next(String T,int* nextval)    //计算T的next数组
{
    int i,j;
    i = 1;
    j = 0;
    nextval[1] = 0;     //初始化  
    while(i<T[0])    //此处T[0]为串T的大小
    {
        if(j == 0||T[i] == T[j] )    //T[i]代表后缀的单个字符  T[j]代表前缀的单个字符 
        {
            ++i;            
            ++j;            
            if(T[i] != T[j])       //若与前缀字符不同
                nextval[i] = j;    //则为j值即原next[i]值
            else 
                nextval[i] = nextval[j];    //若相等,则为nextval[j]值
        }
        else
            j =nextval[j];    //若字符不同,则j值回溯
    }
}

可见相比之前的KMP主要区别在于存next时进行了判断是否与之前的next指向的字符串相等。

猜你喜欢

转载自blog.csdn.net/KNOW_MORE/article/details/88220001
今日推荐