第四章 串【24王道数据结构笔记】

news2024/11/24 12:08:53

1.串的基本概念

  • 串,即字符串 (String) 是由零个或多个字符组成的有限序列。一般记为S='a1a2.....·an'(n>=0)
S="HelloWorld!"
T='iPhone 11 Pro Max?'

其中,S是串名,单引号括起来的字符序列是串的值;a;可以是字母、数字或其他字符;串中字符的个数n称为串的长度。n =0时的串称为空串 。

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

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 < T,返回值<0.

3.串的存储实现 

 3.1静态数组实现

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

#define MAXLEN 255   //预定义最大串长为255
 //静态数组实现(定长顺序存储)
typedef struct{
    char ch[MAXLEN];   //每个分量存储一个字符,每个char字符占1B
    int length;        //串的实际长度
}SString;

3.2 动态数组实现( 堆分配存储)

typedef struct{
    char *ch;          //按串长分配存储区,ch指向串的基地址
    int length;        //串的实际长度
}HString;
void Init(HString& S)
{
    S.ch = (char*)malloc(MAXLEN *sizeof(char));
    S.length = 0;
}

 3.3基本操作的实现

#define MAXLEN 255
 
typedef struct{
    char ch[MAXLEN];   
    int length;       
}SString;
 
// 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. 比较两个串的大小
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
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)
            ++i;
        else 
            return i;    // 返回子串在主串中的位置
    }
    return 0;            //S中不存在与T相等的子串
}
 

4.串的朴素模式匹配

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

4.1朴素模式匹配算法(简单模式匹配算法) 思想:

将主串中与模式串长度相同的子串搞出来,挨个与模式串对比当子串与模式串某个对应字符不匹配时,就立即放弃当前子串,转而检索下一个子串。

算法分析

  • 若模式串长度为m,主串长度为n,则直到匹配成功/匹配失败最多需要(n-m+1)*m 次比较最坏时间复杂度: 0(nm)
  • 最坏情况:每个子串的前m-1个字符都和模式串匹配,只有第m个字符不匹配。
  • 比较好的情况:每个子串的第1个字符就与模式串不匹配
     

 4.2串的朴素模式匹配算法代码实现:

// 在主串S中找到与模式串T相同的子串并返回其位序,否则返回0
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 = i - j + 2; j=1; 
        }   
    }   
    if(j>T.length) 
        return i - T.length;   
    else       
        return 0;
}


//引入辅助变量k,指向子串的首字符
// 在主串S中找到与模式串T相同的子串并返回其位序,否则返回0
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;
}

时间复杂度:设模式串长度为m,主串长度为n

  • 匹配成功的最好时间复杂度:O(m)
  • 匹配失败的最好时间复杂度:O(n)
  • 最坏时间复杂度:O(mn)

5. KPM算法

算法思想

  • 朴素模式匹配算法的缺点:当某些子串与模式串能部分匹配时,主串的扫描指针 i 经常回溯,导致时间开销增加。最坏时间复杂度O(mn)
  • KMP算法:当子串和模式串不匹配时,主串指针i不回溯,模式串指针j = next[ j ]算法平均时间复杂度:O(m+n)

5.1求模式串的next数组

相关概念:

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

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

最长公共前后缀(部分匹配值):字符串的前缀和后缀的交集中最长的

前部分匹配值写成数组形式,就得到了部分匹配值表(partial match, PM)

使用PM表时,每当匹配失败需要去寻找它前一个元素的部分匹配值,这样使用起来有些不方便,所以将PM表右移一位,得到next数组,右移后第一位的空缺用-1填充,这样相当于将子串的比较指针回退到next[j+1] +1,有时为了使计算简介方便,会将next数组整体加1,如果next数组是从位序0开始的不需要加1

  • next[j]的含义是,当第j个字符匹配失败,则跳转到子串的next[j]位置重新与主串当前值进行比较。

 KMP算法的核心是求next数组,下面给出next数组有三种求法 

5.1.1第一种next数组求法

首先看下面这个例子(1),当字符串匹配失败时,如果按照朴素版的话,模式串后移且两个指针初始化如(2)所示。但仔细观察模式串可以发现,绿框表示的是两个子字符串是相同的,那么只要将头子字符串直接移到 j 指针之前,那么依然能保证 j 指针前的字符串是匹配度如(3)所示。对照两者的区别可以发现,其中 i 指针是保持不变的,j 指针通过回溯去寻找相同的首字符串,通过这种思想做到了主串不回溯,时间复杂度可以大幅度降低至O(N+M)。

 对于如何让指针回溯的适合的位置,需要一个辅助数组next,next存放的是两字符串匹配时发生错误后指针 j 跳转的位置也就是态(1)转移到状态(3)。在计算next值之前,首先需要默认模式串从数组下标1开始存储,同样的next数组与存储数组相对应也是从1开始计算赋值为0。因为第一个字符如果匹配失败j指着跳转到0出也相当于模式串右移,下标0是用来判断没有匹配到的字符。如上模式串中的第一个c,虽然没有首字符串与它匹配,但是依然需要对它进行赋值,以便指针 j 匹配失败后制动到第一个字符的位置。匹配代码如下所示。

// 获取模式串T的next[]数组
void getNext(SString T, int next[]){ 
{
    for (int j = 0, i = 1; i < n;)
    {
        if (j == 0 || s[i] == s[j])
        {
            i++;        // 数组右移。存放当前指向元素的next
            j++        // 因为为了方便,数组整体加1
            next[i] = j;// 用下一时刻数组存放当前匹配的前缀位置+1
        }
        else    j = next[j];
    }
}

 根据上述代码模拟出模式串求next数组的过程,这个过程类似于模式串自己对自己进行KMP匹配,i指针及其之前的next值都已经求出来了,因此i指针指向的字符即使找不到匹配的字符,也可以让j指针回到0处赋值。

完整代码如下


// 获取模式串T的next[]数组
void getNext(SString T, int next[]){ 
{
    for (int j = 0, i = 1; i < n;)
    {
        if (j == 0 || T.ch[i] == S.ch[j])
        {
            j+, i++;
            next[i] = j;    // 用下一时刻数组存放当前匹配的前缀位置+1
        }
        else    j = next[j];
    }
}


// KPM算法,输出主串S中所有模式串T的位序,没有则输出0
void index_KMP(SString T, SString S)
{
    int i = 1, j = 1;
    bool flag = true;
    int next[T.length+1]; 
    getNext(T, next); 
    while (i <= S.length)
    {
        if (j == 0 || T.ch[j] == S.ch[i]) i++, j++;
        else    j = next[j];
        
        if (j > T.length)
        {
            cout << i - T.length - 1 << endl;    // 匹配成功,输出子串位置
            j--, i--;// 回退,寻找下一子串
            j = next[j];
            flag = false
        }
    }
    if (flag) cout << 0 << endl;
}
 
int main()
{
    SString S={"ababcabcd", 9};
	SString T={"bcd", 3};
    index_KMP(S, T);
    return 0;
}

5.1.2 第二种next数组求法

第一种next数组是存储回溯位置,在《算法导论》中提供了另一种next求法,next数组中存储的是最大的首字符串长度。如下代码所示,同样的从下标1开始存储,0为没有匹配字符,这里比较的是L+1和R的关系,当L+1与R不匹配返回让L继续回溯。当回溯完后还需要让L+1和R进行比较,因为可能原本字符匹配失败,但在回溯后匹配成功了。
 

void get_next()
{
    for (int l = 0, r = 2; r <= n; r ++ )
    {
        while (l != 0 && s[l + 1] != s[r])    l = next[l];
        if (s[l + 1] == s[r])    l++;
        next[r] = l;
    }
}

由于第二种KMP算法从始至终比较的都是L+1,回溯的是L,因此即使两个字符串匹配完成后,L和R指针依然指向两个字符串末字符,因此没必要确定位置,直接让L进行回溯即可。至于匹配过程,与上述匹配大同小异,需要额外注意L+1指针。

void index_KMP()
{
    for (int l = 0, r = 1; r <= m; r ++ )    //m为主串t的长度
    {
        while (l != 0 && s[l + 1] != t[r])  l = next[l];
        if (s[l + 1] == t[r]) l ++;
        if (l == n)    //可能存在多匹配成功
        {
            printf("%d ", r - n);
            l = next[l];
        }
    }
}

5.1.3第三种next数组求法

可以看到next[0]=-1,这个时候只需要让第一种计算出来的整体减1,

来自2024王道数据结构题P115第6题:

串“ababaaababaa”的next数组为()

A、-1,0,1,2,3,4,5,6,7,8,8,8        B、-1,0,1,0,1,0,0,0,0,1,0,1

C、-1,0,0,1,2,3,1,1,2,3,4,5        D、-1,0,1,2,-1,0,1,0,1,2,1,1,2,3

按照第一种next数组求法结果为0 1 1 2 3 4 2 2 3 4 5 6,让每一个数都-1正好为答案:C 

或者直接手算最大公共前后缀然后整体右移

优化KMP算法

        在处理一些特殊的字符时,KMP算法还可以继续优化。如下案例,当模式串匹配遇到失败时,按照第一种匹配使得模式串后移一位。原本b!=a,在回溯后依然是b!=a……如果按照nextval数组,可以做到一步到位。

与原先的求next数组一样,nextval代码进行了两次比较,让后序的字符下标等于最初的下标,也就是上述的下标2~4全部都等于下标1的值。

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

 next数组优化为nextval数组

首先nextval[0] = 1,然后从前往后依次求解,当遍历到的元素等于该元素的next元素时,此时next必然也不匹配,需要继续寻找可能匹配的,此时nextval[next[j]]早已求得(在前面),所以直接将nextval[j]赋值为nextval[next],不需要再继续递归

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1217770.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DocCMS keyword SQL注入漏洞复现 [附POC]

文章目录 DocCMS keyword SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 DocCMS keyword SQL注入漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测…

idea中误删.iml和.idea文件,如何处理

目录 一、问题描述 二、解决方案 1、理论知识 &#xff08;1&#xff09;.iml 文件 &#xff08;2&#xff09;.idea文件 2、操作环境 3、操作步骤 &#xff08;1&#xff09;找到【Maven】工具按钮 &#xff08;2&#xff09;点图标&#xff0c;重复导入maven项目&am…

Pikachu漏洞练习平台之XXE(XML外部实体注入)

目录 什么是 XML&#xff1f; 什么是DTD&#xff1f; 什么是XEE&#xff1f; 常见payload 什么是 XML&#xff1f; XML 指可扩展标记语言&#xff08;EXtensible Markup Language&#xff09;&#xff1b; XML 不会做任何事情&#xff0c;而是用来结构化、存储以及传输信息…

promise时效架构升级方案的实施及落地 | 京东物流技术团队

一、项目背景 为什么需要架构升级 promise时效包含两个子系统&#xff1a;内核时效计算系统&#xff08;系统核心是时效计算&#xff09;和组件化时效系统&#xff08;系统核心是复杂业务处理以及多种时效业务聚合&#xff0c;承接结算下单黄金流程流量&#xff09;&#xff…

SPASS-参数估计与假设检验

参数估计 点估计 点估计用样本统计量的值直接作为总体参数的估计值。如用样本均值直接作为总体均值的估计值,用样本方差直接作为总体方差的估计值等。 常用的点估计法 (1)矩估计法 (2)极大似然估计法 (3)稳健估计法 区间估计 因为点估计直接用样本估计值作为总体参数…

苹果MAC安装绿盾出现问题,安装时没有出现填服务器地址的页面,现在更改不了也卸载不了绿盾 怎么处理?

环境: Mac mini M1 Mac os 11.0 绿盾v6.5 问题描述: 苹果MAC安装绿盾出现问题,安装时没有出现填服务器地址的页面,现在更改不了也卸载不了绿盾 怎么处理? 解决方案: 大部分企业是Windows和Mac终端混合使用,在进行文档加密管理时通常会遇到不兼容的现象,而为了统一…

Ubuntu 18.04无网络连接的n种可能办法

文章目录 网络图标消失&#xff0c;Ubuntu无网络连接VMware上Ubuntu18.04&#xff0c;桥接了多个网卡&#xff0c;其中一个用来上网&#xff0c;均设置为静态ip网络桥接链路没有接对路由不对 网络图标消失&#xff0c;Ubuntu无网络连接 sudo service network-manager stop sud…

python趣味编程-5分钟实现一个谷歌恐龙游戏(含源码、步骤讲解)

Python 恐龙游戏是为想要学习 Python 的初学者创建的。该项目系统使用了 Pygame 和 Random 模块。 Pygame 是一组跨平台的 Python 模块,专为编写视频游戏而设计。 Python 中的 Dino Game有一个任务记录,其中包含图片文档和 Python 内容(dino.py)。 GUI 使用 pygame 库。

Linux C 线程间同步机制

线程间同步机制 概述保护机制互斥锁创建互斥锁  pthread_mutex_init加锁  pthread_mutex_lock解锁  pthread_mutex_unlock删除锁  pthread_mutex_destroy 条件变量创建条件变量  pthread_cond_init激活条件变量  pthread_cond_signal等待条件变量  pthread_cond_…

Sentinel规则

一、服务熔断测试 例子: application.properties配置文件 server.port8083spring.application.nameorder#spring.cloud.nacos.discovery.server-addrhttp://192.168.44.64:80spring.cloud.nacos.discovery.server-addrlocalhost:8848spring.cloud.sentinel.transport.port999…

基于单片机的智能家居安保系统(论文+源码)

1.系统设计 本次基于单片机的智能家居安保系统设计&#xff0c;在功能上如下&#xff1a; 1&#xff09;以51单片机为系统控制核心&#xff1b; 2&#xff09;温度传感器、人体红外静释电、烟雾传感器来实现检测目的&#xff1b; 3&#xff09;以GSM模块辅以按键来实现远/近程…

Nginx(四) absolute_redirect、server_name_in_redirect、port_in_redirect 请求重定向指令组合测试

本篇文章主要用来测试absolute_redirect、server_name_in_redirect和port_in_redirect三个指令对Nginx请求重定向的影响&#xff0c;Nginx配置详解请参考另一篇文章 Nginx(三) 配置文件详解 接下来&#xff0c;在Chrome无痕模式下进行测试。 测试1&#xff1a;absolute_redi…

yolov5模型代码怎么修改

yaml配置文件 深度乘积因子 宽度乘积因子 所有版本只有这两个参数的不同&#xff0c;s m l x逐渐加宽加深 各种类型层参数对照 backbone里的各层&#xff0c;在这里解析&#xff0c;只需要改.yaml里的各层参数就能控制网络结构 修改网络结构 第一步&#xff1a;把新加的模块…

用人话讲解深度学习中CUDA,cudatookit,cudnn和pytorch的关系

参考链接 本人学习使用&#xff0c;侵权删谢谢。用人话讲解深度学习中CUDA&#xff0c;cudatookit&#xff0c;cudnn和pytorch的关系 CUDA CUDA是显卡厂商NVIDIA推出的运算平台。 CUDA™是一种由NVIDIA推出的通用并行计算架构&#xff0c;是一种并行计算平台和编程模型&…

在屏幕上打印杨辉三角

我们先来看看杨辉三角长什么样的&#xff1a; 我们一起来看一看它的规则: 1. 每一行的第一个数字和最后一个数字都是1 2. 从第三行开始&#xff0c;中间的数字和最后一个数字等于它上方两个数字之和 我们可以很好的发现下面的数字就是由该数字上面一行和上一列加上该数字的…

redis设置密码

首先到redis的下载目录下&#xff1a;运行 redis-cli 查看redis密码&#xff1a; config get requirepass 设置redis密码为123456&#xff1a; config set requirepass 123456

SQL中的数据类型和规范化,助力数据存储优化

大家好&#xff0c;目前优化数据存储对于获得良好的性能始终至关重要&#xff0c;选择合适的数据类型并应用正确的规范化过程对于决定其性能至关重要。本文将介绍最重要和最常用的数据类型和规范化过程。 一、SQL中的数据类型 SQL中主要有两种数据类型&#xff1a;字符串和数…

【前沿学习】美国零信任架构发展现状与趋势研究

转自&#xff1a;美国零信任架构发展现状与趋势研究 摘要 为了应对日趋严峻的网络安全威胁&#xff0c;美国不断加大对零信任架构的研究和应用。自 2022 年以来&#xff0c;美国发布了多个零信任战略和体系架构文件&#xff0c;开展了多项零信任应用项目。在介绍美国零信任战略…

Numpy数组进阶_Python数据分析与可视化

Numpy数组进阶 Numpy的广播机制高级索引整数数组索引布尔索引花式索引 数组迭代 Numpy的广播机制 广播 (Broadcast) 是 numpy 对不同形状 (shape) 的数组&#xff0c;进行数值计算的方式。 对数组的算术运算通常在相应的元素上进行&#xff0c;当运算中的 2 个数组的形状不同时…

DNS1(Bind软件)

名词解释 1、DNS&#xff08;Domain Name System&#xff09; DNS即域名系统&#xff0c;它是一个分层的分布式数据库&#xff0c;存储着IP地址与主机名的映射 2、域和域名 域为一个标签&#xff0c;而有多个标签域构成的称为域名。例如hostname.example.com&#xff0c;其…