【数据结构笔记】8.串

文章目录

    • 8.1 串的定义
    • 8.2 串的基本操作
    • 8.3 串的存储
      • 8.3.1 串的顺序存储
      • 8.3.2 串的链式存储
    • 8.4 串的基本操作的实现
      • 8.4.1 求子串
      • 8.4.2 比较操作
      • 8.4.3 定位操作
    • 8.5 朴素模式匹配算法
    • 8.6 KMP算法

8.1 串的定义

串,即字符串(String)是由零个或多个字符组成的有限序列。一般记为 S = ′ a 1 a 2 . . . . . . a n ′ ( n ≥ 0 ) S = 'a_1a_2......a_n'(n \geq0) S=a1a2......an(n0)。其中,S是串名,单引号括起来的字符序列是串的值; a i a_i ai可以是字母、数字或其他字符;串中字符的个数n称为串的长度。 n = 0 n=0 n=0时的串成为空串(用 ∅ \emptyset 表示)。

注意:有的地方用双引号作为字符串的界限符(如Java、C),有的地方用单引号作为字符串的界限符(如Python)。

  • 子串:串中任意个连续的字符组成的子序列。

  • 主串:包含子串的串。

  • 字符在主串中的位置:字符在串中的序号。

  • 子串在主串中的位置:子串的第一个字符在主串中的位置。

串 V.S 线性表:

  • 串是一种特殊的线性表,数据元素之间呈线性关系。
  • 串的数据对象限定为字符集(如中文字符、英文字符、数字字符、标点字符等)。
  • 串的基本操作,如增删改查等通常以子串为操作对象

8.2 串的基本操作

  • StrAssign(&T, chars):赋值操作。把串T赋值为chars。

  • StrCopy(&T, S):复制操作。由串S复制得到串T。

  • StrEmpty(S):判空操作。若S为空串,则返回TRUE,否则返回FALSE。

  • StrLength(S):求串长。返回串S的元素个数。

  • ClearString(&S):清空操作。将S清为空串。

  • DestroyString(&S):销毁串。将串S销毁(回收存储空间)。

  • Concat(&T, S1, S2):串联接。用T返回由S1和S2联接而成的新串。

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

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

  • StrCompare(S,T):比较操作。若S>T,则返回值>0;若S=T,则返回值=0;若S

任何数据存到计算机中一定是二进制数。需要确定一个字符和二进制数的对应规则,这就是“编码”。采用不同的编码方式,每个字符所占空间不同,考试中只需默认每个字符占1B即可。

8.3 串的存储

8.3.1 串的顺序存储

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

#define MAXLEN 255		//预定义最大串长为255
typedef struct{
    char ch[MAXLEN];	//每个分量存储一个字符
    int length;			//串的实际长度
}SString;

动态数组实现(堆分配存储),用完需要手动free。

typedef struct{
    char *ch;		//按串长分配存储区,ch指向串的基地址
    int length;		//串的长度
}HString;

HString S;
S.ch = (char *)malloc(MAXLEN * sizeof(char));
S.length = 0;

8.3.2 串的链式存储

//存储密度低:每个字符1B,每个指针4B。
typedef struct StringNode{
    char ch;		//每个结点存1个字符
    struct StringNode * next;
}StringNode, * String;

//可适当提高每个结点所携带的信息量,此时存储密度提高。
typedef struct StringNode{
    char ch[4];		//每个结点存多个字符
    struct StringNode * next;
}StringNode, * String;

8.4 串的基本操作的实现

8.4.1 求子串

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

// 使用静态数组实现
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;
}

8.4.2 比较操作

若S>T,则返回值>0;若S=T,则返回值=0;若S

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
}

8.4.3 定位操作

若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0。

int Intex(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);	//从主串S中提取一个与串T长度相同的子串sub
        if(StrCompare(sub, T) != 0)
            ++i;
        else
            return i;
    }
    return 0;	//S中不存在与T相等的子串。
}

8.5 朴素模式匹配算法

主串长度为n,模式串长度为m。

朴素模式匹配算法:将主串中所有长度为m的子串依次与模式串对比,直到找到一个完全匹配的子串,或所有子串都不匹配为止。

int Index(SString S, SString T){
    int i=1, j=1;
    while(i<=S.length && j<=T.length){
        if(S.ch[i] == T.ch[j]){		//继续比较后继字符
            ++i;
            ++j;
        }
        else{
            //若当前子串匹配失败,则主串指针i指向下一个子串的第一个位置,
            //模式串指针j回到模式串的第一个位置
            i = i-j+2;
            j = 1;
        }
    }
    if(j > T.length)
    	return i-T.length;
    else
        return 0;
}

最坏的情况,每个子串都要对比m个字符,共n-m+1个子串,复杂度 = O((n-m+1) * m) = O(nm)(注:很多时候n>>m)。

8.6 KMP算法

由D.E.Knuth, J.H.Morris和V.R.Pratt提出,因此成为KMP算法。

next数组:当模式串的第j个字符匹配失败时,令模式串跳到next[j]再继续匹配。

串的前缀:包含第一个字符,且不包含最后一个字符的子串。

串的后缀:包含最后一个字符,且不包含第一个字符的子串。

当第j个字符匹配失败,由前1~j-1个字符组成的串记为S,则next[j] = S的最长相等前后缀长度+1.

特别地,next[1] = 0, 且next[2]往往等于1。

//KMP算法
int Index_KMP(SString S, SString T, int next[]){
    int i=1, j=1;
    while(i<=S.length && j<=T.length){
        if(j==0 || S.ch[i]==T.ch[j]){	//继续比较后继字符
            ++i;
            ++j;
        }
        else
            j = next[j];				//模式串向右移动
    }
    if(j > T.length)
        return i-T.length;				//匹配成功
    else
        return 0;
}
//求模式串T的next数组
void get_next(SString T, int next[]){
    int i=1, j=0;
    next[1]=0;
    while(i<T.length){
        if(j==0 || T.ch[i]==T.ch[j]){
            ++i;
            ++j;
            //若pi=pj,则next[j+1] = next[j]+1
            next[i]=j;
        }
        else
            j=next[j];	//否则令j=next[j],循环继续
    }
}
//根据next数组求改进的nextval数组
nextval[1] = 0;
for(int j=2; j<=T.length; j++){
    if(T.ch[next[j]] == T.ch[j])
        nextval[j] = nextval[next[j]];
    else
        nextval[j] = next[j];
}
  • 朴素模式匹配算法的缺点:当某些子串与模式串能部分匹配时,主串的扫描指针i经常回溯,导致时间开销增加。最坏时间复杂度O(nm);
  • KMP算法:当子串和模式串不匹配时,主串指针i不回溯,模式串指针j = next[j],算法平均时间复杂度为O(n+m)。

你可能感兴趣的:(数据结构,数据结构,笔记,java)