【数据结构】第四章:串


【实验时使用C++操作】
【C++程序代码请观看[数据结构实验集]】
【用自己习惯的方式来记忆各种算法,而不是背课本!!!】

4.1 串的定义 和 基本操作

4.1.1 串的定义

  • 字符串,0个或多个字符组成的有限序列
  • 术语:
    • 子串:串中人一个连续的字符组成的子序列
    • 主串:包含子串的串
    • 字符在主串中的位置:字符在串中的位置
    • 子串在主串中的位置:子串的第一个字符在主串中的位置
  • 串和线性表:
    • 串是一种特殊的线性表,数据元素之间呈线性关系
    • 串的数据对象限定为字符集(中文字符、英文字符、数字字符、标点字符)
    • 串的基本操作(增删改查)通常以子串作为操作对象

4.1.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销毁(回收存储空间)
  • 串联接:Contact(&T,S1,S2) 用T返回由S1和S2联接而成的新串
  • 求子串:SubString(&Sub,S,pos,len) 用Sub返回串S的第pos个字符起长度为len的子串
  • 定位操作:Index(S,T) 若主串S中存在和T相同的子串,则返回他在主串S中第一次出现的位置;否则函数值为0
  • 比较操作:StrCimpare(S,T) 若S>T,返回值>0;若S=T,返回值=0;若S<T,返回值<0

4.1.3 字符集编码

  • 任何数据存储到计算机中一定是二进制数
  • 编码:确定一个字符和二进制数的对应规则
  • 字符集:英文字符–ASCII字符集,中英文–Unicode字符集

4.2 串的存储结构 和 基本操作的实现

【王道教材采用——静态数组】

4.2.1 串的顺序存储

  • 静态数组实现(定长顺序存储表示)
  • 动态数组实现(堆分配存储表示)

4.2.2 串的链式存储

  • 每个节点存1个字符(存储密度低:每个制度1B,每个指针4B)
  • 每个节点存多个字符(存储密度提高)

4.2.3 基本操作的实现

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

// 求子串
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;
}
  • 比较操作:
//比较操作。 若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;
}
  • 定位操作(简单匹配):

【建议使用朴素模式匹配算法,其原理一致】

int Index(SString S, SString T){
    int u=1; n=StrLength(S), m=StrLength(T);
    SString sub;    // 用于暂存子串
    while(i<=n-m+1){
        SubString(sub,S,i,m);
        if(StrCompare(sub,T) != 0)
            i++;
        else
            return i;    // 返回子串在主串中的位置        
    }
    return 0;    // S中不存在与T相等的子串
}

4.3 串的朴素模式匹配算法

  • 模式串:尝试在珠串中找到的串,未必存在
  • 串的模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置。

4.3.1 简单的模式匹配算法

int Index(SString S, SString T){
    int k=1;
    int i=k; j=1;
    while(i<=S.length && j<=T.length){
        if(S.ch[i] == T.ch[j]){
            i++; j++;    // 继续比较后继字符        
        } else{
            k++; i=k; j=1;    // 检查下一个子串
        }
    }
    if(j > T.length)
        return k;
    else
        return 0;
}

4.3.2 朴素模式匹配算法性能分析

  • 模式串长度为m,主串长度为n,则:
    • 匹配成功的最好时间复杂度:O(m)
    • 匹配失败的最好时间复杂度:O(n-m+1) = O(n-m) ≈ O(n)
    • 匹配成功/失败的最坏时间复杂度:O(nm)

4.4 KMP算法

  • 朴素模式匹配算法的缺点:某些子串与模式串能部分匹配时,主串的扫描指针i经常回溯,导致时间开销增加。

4.4.1 字符串的前缀、后缀、部分匹配值

  • 前缀:除最后一个字符以外,字符串的所有头部子串
  • 后缀:除第一个字符字符外,字符串的所有尾部子串
  • 部分匹配值:字符串的前缀和后缀的最长相等前后缀长度 → next数组 → 当字符不匹配时,按照公式算出子串需要后移的位数:移动位数 = 已匹配的字符串 - 对应的部分匹配值

4.4.2 KMP算法的原理

  • KMP算法:当子串和模式串不匹配时,主串指针i不回溯,模式串指针j=next[j]

  • KMP算法平均时间复杂度:O(m+n)

  • next数组手算方法:当第j个字符匹配失败,由前1~j-1个字符组成的串记为S,则:next[j]=S的最长相等前后缀长度+1;特别的,next[1]=0

  • KMP算法步骤:
    (1)计算next数组
    (2)根据主串S,模式串T,next数组 进行KMP匹配

// 求模式串T的next数组
void get_next(String 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++;
            next[i]=j;    // 若pi=pj,则 next[j+1]=next[j]+1
        }
        else
            j = next[j];    // 令j=next[j],循环继续
    }
}

// KMP算法
int Index_KMP(SString S, SString T){
    int i=1, j=1;
    int next[T.length+1];
    get_next(T,next);    // 求模式串的next数组
    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;
}

4.4.3 KMP算法的进一步优化(优化next数组)

  • 计算next数组修正值的算法:
void get_nextval(String T, int nextval[]){
    int i=1, j=0;
    nextval[1]=0;
    while(i<T.length){
        if(j==0 || T.ch[i]==T.ch[j]){
            i++;j++;
            if(T.ch[i] != T.ch[j])
                nextval[i]=j;
            else
                nextval[i]=nextval[j];
        }
        else
            j=nextval[j];
    }
}

【参考资料】
「天勤公开课」KMP算法易懂版_哔哩哔哩_bilibili
KMP算法之求next数组代码讲解_哔哩哔哩_bilibili

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值