数据结构王道笔记③串

5 阅读6分钟

本章只需要掌握王道小题

一、基本概念

  1. 子串,主串
  2. 字符在串中的位置:字符在串中的序号,从1开始
  3. 子串在主串中的位置:以子串的第1个字符在主串中的位置来表示
  4. 空格串:由一个或多个字符组成,长度为空格的个数
  5. 空串:长度为0

二、基本操作

strAssign(&T,chars):赋值操作。把串T赋值为chars。
strCopy(&T,S):复制操作。由串s复制得到串T。
StrEmpty(S):判空操作。若s为空串,则返回 TRUE,否则返回 FALSE。
StrCompare(S,T):比较操作。若 S>T,则返回值>0;若 S=T,则返回值=0;若 S<T,则返回值<0。(ASCLL)
strLength(S):求串长。返回串s的元素个数。
Substring(&Sub,S,pos,len):求子串。用 sub 返回串s的第pos个字符起长度为len的子串。concat(&T,S1,S2):串联接。用T返回由 s1 和 s2 联接而成的新串。
Index(S,T):定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值为0
ClearString(&S):清空操作。将s清为空串。
DestroyString(&S):销毁串。将串S销毁。

三、存储结构

(一)定长顺序存储(静态数组)

超过MAXLEN的串长会被截断
串长表示:①用int length ②在串值后面加一个不计入串长的结束符"\0",此时串长为隐含值

#define MAXLEN 255 typedef struct{
    char ch[MAXLEN];//每个分量分配一个字符 
    int length;//串的实际长度
}SString;
//运行结束自动回收空间
//一般char[0]废弃不用,从1开始的优点是字符位序和数组下标相同

1.求子串

bool SubString(SString &Sub,SString S,int pos,int len) {
    //子串范围越界
    if(pos+len-1 > S.length)
        return false;
    for(int i=pos; i<pos+len; i++)
        Sub.ch[i-pos+1] = S.ch[i];
    Sub.length = len;
    return true;
}

2.比较两个串的大小

//若S>T则返回值>0,S<T则返回值<0,S=T则返回值=0
int StrCompare(SString S,SString T) {
   for(int i=1;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.定位操作 若主串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) {
        SubString(sub,S,i,m);//取子串
        if(StrCompare(sub,T)!=0)//两串相同则返回0 
            ++i;//若不匹配则i++
        else return i;//匹配成功返回i
    }
    return 0;
}

(二)堆分配存储(动态数组)

typedef struct{
    char *ch//按串长度分配存储区,ch指向串的基地址
    int length;//串的长度
}HString;
HString S;
S.ch = (char*)malloc(MAXLEN * sizeof(char));//申请堆区空间
S.length=0;
//需要用free()回收空间

(三)块链存储

链表

typedef struct StringNode{
    char ch; //每个结点存一个字符
    struct StringNode *next;
}StringNode,*String;
//缺点:ch1B,next4B,存储密度低
//改进:存储密度提高
typedef struct StringNode{
    char ch[4]; //每个结点存多个字符
    struct StringNode *next;
}StringNode,*String;
//若最后一个字符不够4个填不满,可以把#放进去

四、串的模式匹配

在主串中找到与模式串相同的子串,并返回其所在位置
此处采用定长顺序存储
Index已经实现了

(一)简单的模式匹配算法

暴力,不依赖于其他串操作
主串长n,模式串m,将主串中所有长度为m的子串(最多n-m+1个)依次与模式串对比
若n远远大于m,则最多进行n-m+1次匹配,每次最多进行m次比较,最坏时间复杂度O(mn)

int Index(SString S,SString T){
    int i=1;j=1;//计数指针,表示主串S和模式串T中当前待比较的字符位置
    while(i<=S[0]&&j<=T[0]){
        if(S[i]==T[j]){
            ++i;++j; //继续比较后继字符 
        }
        else{//当前子串匹配失败
            i=i-j+2;j=1; //i指向下一个子串的第一个位置,j回到模式串的第一个位置
            //i-(j-1)+1,j-1表示当前已匹配次数
        }
    }
    if(j>T[0])//j所指的位置超出了模式串长度,匹配成功
        return i-T[0];//返回当前子串第一个字符的位置
    else
        return 0//没找到
}

(一)KMP算法(只考手算next数组选择题)

最坏时间复杂度O(m+n),其中,求next数组为O(m),模式匹配最坏为O(n)
根据模式串T,求出next数组,利用next数组进行匹配(主串指针不回溯,next反映模式串指针i的变化)
假设串的编号从1开始,若从0开始,则next数组需要整体-1

1.手算next数组

next[0]不适用,next[1]=0,next[2]=1是固定的
从next[3]开始,在不匹配的位置前边,划一根分界线,主串和线不动,模式串一步一步后退,直到分界线之前能匹配,或模式串完全在分界线右边。此时j指向哪里,next值就是多少
next[3]即在2和3之间画一条线,若模式串完全到了分界线右边,此时第三个字符匹配失败,则next[3]=1

2.KMP算法代码
//next[j]的含义是当模式串的第i个字符失配时,跳到next[j]位置继续比较
void get_next(char T[],int next[]){ 
    i=1;
    next[1]=0;
    j=0;
    while(i<=T[0]){ //T[0]用于保存字符串的长度
        if(j==0||T[i]==T[j]){
            ++i;
            ++j;
            next[i]=j;
        }
        else j=next[j];
    }
}

int KMP(char S[],char T[],int next[],int pos) {
    i=pos;
    j=1;
    while(i<=S[0]&&j<=T[0]){ 
        if(j==0||S[i]==T[j]){//j=0时,下一步是两个都++
            ++i;
            ++j;
        }
        else//匹配失败时,主串指针不回溯,模式串指针改变
            j=next[j];
    }
    if(j>T[0]) 
        return i-T[0];
    else 
        return 0;
}
3.KMP算法的优化

做next变化的前后为相同字母时,注定无法匹配,可以避免。若出现 Pj=Pnext[j],则需要再次递归,将next[j]修正为next[next[j]],直至两者不相等为止,更新后的数组命名为 nextval。计算 next 数组修正值的算法如下,此时匹配算法不变

(1)手算nextval数组

nextval[1]=0,其他的,若当前元素 不等于 当前元素的next[j]序号处的元素,则让nextval=next;若相等,则让nextval=nextval[next[j]]

(2)优化后的KMP

用nextval替代next

void get_nextval(SString T,int nextval[]){
    int i=1,j=0;
    nextval[1]=0;
    while(i<r.length){
        if(j==0||T.ch[i]==T.ch[j]){
            ++i; ++j;
            if(T.ch[i]!=T.ch[i]) nextval[i]=j;
            else nextval[i]=nextval[j];
        }
        else
            i=nextval[i];
    }
}