数据结构基础内容-----第五章 串

news2024/11/23 18:58:11

文章目录

  • 串的比较
  • 串的抽象数据类型
    • 串的顺序存储结构
    • 朴素的额模式匹配算法
    • kmp模式匹配算法

在计算机编程中,串(String)是指由零个或多个字符组成的有限序列。它是一种基本的数据类型,在许多编程语言中都得到了支持和广泛应用。通常情况下,我们使用单引号或双引号来表示一个串,例如:“Hello World” 或者 ‘123456’。

串可以进行各种操作,比如拼接、截取、查找、替换等等。在实际应用中,串经常用于处理文本、密码等方面。例如,在网站开发中,我们需要对用户输入的字符串进行验证、过滤、加密等操作。此外,在数据处理及分析方面,也需要用到串类型。

串的比较

在计算机编程中,字符串的比较通常是指比较两个字符串的字典序。字典序比较适用于对字符串进行排序和查找操作。

具体来说,如果我们要比较两个字符串s1和s2的字典序,可以按照以下步骤进行:

从左到右依次比较s1和s2的每个字符。
如果当前字符相等,则比较下一个字符。
如果当前字符不相等,则比较它们的ASCII码值。如果s1当前字符的ASCII码值小于s2当前字符的ASCII码值,则s1小于s2;反之则s1大于s2。
如果s1或s2已经比较完了所有字符,且前面所有字符都相等,则长度较短的字符串小于长度较长的字符串。
例如,比较"abc"和"abd"的字典序时,首先比较第一个字符’a’和’b’,发现它们不相等,因此可以确定“abc”小于“abd”。

串的抽象数据类型

串的抽象数据类型(ADT)是一种数据结构,它定义了对字符串进行操作的抽象接口。常见的串操作包括插入、删除、查找、替换和比较等。

ADT String {
    data:
        串的字符集,每个元素都属于此集合中的某个字符
    operation:
        StrAssign(T, chars)   // 将T赋值为由chars组成的串
        StrCopy(T, S)         // 复制串S到T
        StrLength(S)          // 返回串S的长度
        StrEmpty(S)           // 判断串S是否为空
        Concat(T, S1, S2)     // 将串S1和S2连接起来,结果存在T中
        SubString(Sub, S, pos, len)  // 返回串S从pos开始长度为len的子串Sub
        StrCompare(S1, S2)    // 比较串S1和S2的大小关系,返回0表示相等,返回正数表示S1大于S2,返回负数表示S1小于S2
        Index(S, T, pos)      // 在串S中从pos位置开始查找串T,返回T在S中第一次出现的位置,如果没找到则返回-1
        Replace(S, T, V)      // 在串S中查找所有的T,并将其替换为V
        CountChar(S, ch)      // 统计串S中某个字符ch出现的次数
        DeleteChar(S, pos)    // 删除串S中指定位置pos的字符
        Insert(S, pos, T)     // 在串S的pos位置插入串T
        Reverse(S)            // 反转串S
        Trim(S)               // 去掉串S两端的空格
        ToUpper(S)            // 将串S中的所有字母字符转换为大写
        ToLower(S)            // 将串S中的所有字母字符转换为小写
}

串的顺序存储结构

串的顺序存储结构是将串中的每个字符依次存放在一段连续的存储空间中,通常使用数组来实现。在顺序存储结构中,我们可以通过下标访问或修改某个字符,因此访问速度较快。

以下是一个简单的串的顺序存储结构定义:

#define MAXSIZE 1000   // 定义串的最大长度

typedef struct {
    char data[MAXSIZE];  // 存储串的字符数组
    int length;          // 串的当前长度
} SqString;

其中,data数组用来存储串的每个字符,length表示当前串的长度。对于一个长度为n的串,它的第i个字符存储在data[i-1]的位置,即下标从0到n-1。

串的链式存储结构是通过链表来实现,每个节点存储一个字符,节点之间用指针连接起来。相比于顺序存储结构,链式存储结构可以动态地分配和释放内存,适合存储长度不确定的串。

以下是一个简单的串的链式存储结构定义:

typedef struct node {
    char data;          // 存储字符
    struct node *next;  // 指向下一个节点的指针
} Node, *LinkStr;

// 串的链式存储结构
typedef struct {
    LinkStr head;       // 头指针
    int length;         // 串的当前长度
} LiString;

其中,每个节点包含一个字符和一个指向下一个节点的指针,head指向链表的第一个节点。在使用链式存储结构时,我们需要注意处理指针的赋值、节点的插入和删除等操作,以及避免出现空指针引用等问题。

需要注意的是,在使用链式存储结构时,由于每个节点都需要额外的指针空间来存储,因此占用的内存会比较大。

朴素的额模式匹配算法

朴素的模式匹配算法,也被称为暴力匹配算法或者Brute-Force算法,是一种简单有效的字符串匹配算法。该算法的基本思想是从目标串的第一个字符开始,依次和模式串进行比较,如果匹配失败则将目标串向右移动一个字符,再从目标串的下一个字符开始重新匹配。

具体而言,朴素的模式匹配算法可以按照以下步骤进行:

将模式串p和目标串t的开头对齐。
逐个比较p和t中相应位置的字符,若全部相等,则匹配成功;否则,转到步骤3。
将t向右移动一位,并从第一步开始重复匹配过程,直到找到匹配位置或者t遍历完毕。
下面是一个简单的朴素模式匹配算法的实现:

int BruteForce(string t, string p) {
    int i = 0, j = 0;           // i和j分别指向t和p的当前匹配位置
    int m = t.length(), n = p.length();

    while (i < m && j < n) {    // 循环直到匹配完成或者t已经遍历完毕
        if (t[i] == p[j]) {     // 当前字符匹配成功,继续比较下一个字符
            i++;
            j++;
        } else {                // 当前字符匹配失败,将t向右移动一位重新开始比较
            i = i - j + 1;
            j = 0;
        }
    }

    if (j == n) {               // 匹配成功,返回匹配位置
        return i - j;
    } else {                    // 匹配失败
        return -1;
    }
}

朴素模式匹配算法的时间复杂度为O(mn),其中m和n分别是目标串和模式串的长度。在最坏情况下,算法需要比较mn次才能完成匹配。该算法虽然简单易懂,但是对于大规模的字符串匹配来说效率较低,因此通常不适用于实际应用。

kmp模式匹配算法

KMP算法是一种字符串匹配算法,可以在一个文本串S内查找一个模式串P的出现位置。KMP算法的主要思想是利用已知信息消除匹配过程中的不必要的比较。

具体实现方法如下:

  • 预处理模式串P,得到next数组。next[i]表示当第i个字符匹配失败时,应该移动模式串的指针到哪个位置继续匹配。

  • 在文本串S中查找模式串P。假设当前文本串匹配到了第j个字符,模式串匹配到了第k个字符,如果S[j]和P[k]相等,则继续比较下一个字符;如果S[j]和P[k]不相等,则将模式串的指针移动到next[k]位置继续匹配。

  • 如果模式串匹配成功,返回文本串中匹配的起始位置。

KMP算法的时间复杂度是O(m+n),其中m和n分别是模式串和文本串的长度。KMP算法比朴素的字符串匹配算法效率更高,特别是在模式串较长的情况下。


KMP算法的关键是求出模式串的next数组,其可以在匹配过程中快速移动模式串指针,从而提高算法效率。下面介绍如何计算next数组的值。

对于模式串P,我们定义next[j]表示P[0] ~ P[j-1]这个子串中前后缀的最长公共部分长度。例如,当j=4时,next[4]表示P[0]~P[3]这个子串的最长公共前后缀长度。

具体地,我们可以使用递推的方式来计算next数组的值。假设我们已经计算出了next[0]、next[1]、… 、next[j-1]的值,现在要计算next[j]的值。我们需要找到P[0] ~ P[j-1]的一个真前缀,它同时也是P[0]~P[j-1]的一个真后缀,并且在其中找到长度最长的那个。然后将这个长度赋给next[j]即可。

更具体地,我们可以按照以下步骤进行计算:

  • 让i=0,j=1,next[0]=-1。

  • 如果P[i]=P[j],则将next[j]=next[i]+1,i和j都加1。

  • 如果P[i]≠P[j],则令i=next[i],直到i=-1或者P[i]=P[j]为止。这里需要注意,当i=-1时,说明已经没有更短的真前缀和真后缀相同的子串了,我们将next[j]=0,j加1即可。

  • 重复步骤2和3,直到计算出所有的next值。

最终计算得到的next数组就是模式串的前缀表,它可以在KMP算法中快速移动模式串指针,提高算法效率。


kmp看起来有点难懂,可以通过部分可视化算法网站来实现Dynamic Programming

在这里插入图片描述
例子

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void computeLPSArray(char* pat, int M, int* lps);

void KMPSearch(char* pat, char* txt)
{
    int M = strlen(pat);
    int N = strlen(txt);

    int* lps = (int*)malloc(sizeof(int) * M);
    int j = 0;

    computeLPSArray(pat, M, lps);

    int i = 0;
    while (i < N) {
        if (pat[j] == txt[i]) {
            j++;
            i++;
        }

        if (j == M) {
            printf("Found pattern at index %d\n", i - j);
            j = lps[j - 1];
        }
        else if (i < N && pat[j] != txt[i]) {
            if (j != 0)
                j = lps[j - 1];
            else
                i++;
        }
    }

    free(lps);
}

void computeLPSArray(char* pat, int M, int* lps)
{
    int len = 0;
    int i = 1;
    lps[0] = 0;

    while (i < M) {
        if (pat[i] == pat[len]) {
            len++;
            lps[i] = len;
            i++;
        }
        else {
            if (len != 0) {
                len = lps[len - 1];
            }
            else {
                lps[i] = 0;
                i++;
            }
        }
    }
}

int main()
{
    char txt[] = "ABABDABACDABABCABAB";
    char pat[] = "ABABCABAB";
    KMPSearch(pat, txt);
    return 0;
}


尽管KMP算法已经很优秀了,但是我们仍然可以对其进行进一步的改进。

一种比较常见的改进方法是使用双指针算法,在匹配过程中同时维护文本串指针i和模式串指针j。具体地,当两个字符相等时,i和j同时向后移动,否则根据next数组的值将j移动到相应位置。这种改进可以进一步减少不必要的计算量,提高算法效率。

另外一种改进方法是使用BM算法,其核心思想是通过对比文本串与模式串最后一个字符的距离,选择合适的步长进行移动。例如,当文本串中的一个字符与模式串最后一个字符不匹配时,可以根据该字符在模式串中出现的位置,移动模式串指针到下一个可能匹配的位置。这种改进方法可以进一步提高算法效率,特别是在处理长文本串和短模式串时效果更加明显。

除此之外,还有一些其他的改进方法,如Sunday算法、Horspool算法等。实际上,不同的算法适用于不同的场景,我们可以根据具体情况选择合适的算法进行优化。

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

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

相关文章

STM32之SPI和W25Q128

目录 SPI 介绍 SPI 物理架构 SPI 工作原理 SPI 工作模式 W25Q128 介绍 W25Q128 存储架构 W25Q128 常用指令 W25Q128 状态寄存器 W25Q128 常见操作流程 实验&#xff1a;使用 SPI 通讯读写 W25Q128 模块 硬件接线 cubeMX配置 w25q128_write_nocheck流程图 代码&a…

如何在华为OD机试中获得满分?Java实现【最长回文子串】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

网络原理(八):HTTPS

目录 HTTP 基本工作流程 利用对称密钥进行加密 利用非对称密钥进行加密 引入了第三方权威机构加密 之前在http 协议中说到&#xff1a;我们现在很少有网站直接使用HTTP 协议的&#xff0c;而是使用HTTPS &#xff0c;至于什么原因&#xff0c;本篇会介绍清楚。 HTTPS 其实…

C++11 -- lambda表达式

文章目录 lamaba表达式的引入lambda表达式语法lamabda达式各部分说明捕获列表说明 lamaba表达式底层原理探索 lamaba表达式的引入 在C11之前,如果我们想对自定义类型Goods排序,可以根据姓名,价格,学号按照从大到小或者从小到大的方式排序,可是,这样我们要写额外写6个相关的仿函…

以太坊学习三: Merkle树和验证

Merkle tree简介 Merkle树又称为哈希树&#xff0c;是一种二叉树&#xff0c;由一个根节点、若干中间节点和一组叶节点组成。最底层的叶节点存储数据&#xff0c;在它之上的一层节点为它们对应的Hash值&#xff0c;中间节点是它下面两个子节点的Hash值&#xff0c;根节点是最后…

DAY 66 数据库缓存服务——NoSQL之Redis配置与优化

缓存概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且硬盘也有大小不一的缓存&am…

爆肝整理,最全单元测试-测试用例总结(全覆盖)及拿即用...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

Maven私服仓库配置-Nexus详解

目录 一、什么是Maven私服&#xff1f;二、Maven 私服优势三、Maven 私服搭建四、Sonatype Nexus介绍五、Nexus仓库属性和分类六、Nexus仓库配置以及创建仓库七、Nexus配置用户角色八、Maven SNAPSHOT(快照)九、项目当中配置Nexus上传依赖十、项目当中配置Nexus下载依赖十一、测…

人工智能基础部分20-生成对抗网络(GAN)的实现应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能基础部分20-生成对抗网络(GAN)的实现应用。生成对抗网络是一种由深度学习模型构成的神经网络系统&#xff0c;由一个生成器和一个判别器相互博弈来提升模型的能力。本文将从以下几个方面进行阐述&#xff1…

flutter_学习记录_03_通过事件打开侧边栏

实现类似这样的侧边栏的效果&#xff1a; 可以用Drawer来实现。 1. 在Scaffold组件下设置endDrawer属性 代码如下&#xff1a; import package:flutter/material.dart;class ProductListPage extends StatefulWidget {ProductListPage( {super.key}) ;overrideState<Pro…

首发Yolov8优化:Adam该换了!斯坦福最新Sophia优化器,比Adam快2倍 | 2023.5月斯坦福最新成果

1.Sophia优化器介绍 斯坦福2023.5月发表的最新研究成果,他们提出了「一种叫Sophia的优化器,相比Adam,它在LLM上能够快2倍,可以大幅降低训练成本」。 论文:https://arxiv.org/pdf/2305.14342.pdf 本文介绍了一种新的模型预训练优化器:Sophia(Second-order Clippe…

低资源方面级情感分析研究综述

文章目录 前言1. 引言2. 问题定义、数据集和评价指标2.1 问题定义2.2 任务定义2.3 常用数据集 3. 方面级情感分析的方法3.1 **方面词抽取**3.1.1 基于无监督学习的方法3.1.1.1 基于规则的方面词抽取3.1.1.2 基于统计的方面词抽取 3.1.2 基于有监督浅层模型的方法3.1.3 基于有监…

【C++初阶】类和对象(下)之友元 + 内部类 + 匿名对象

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

一台服务器通过nginx安装多个web应用

1.首先安装nginx网站服务器 yum install nginx 2.nginx 的主配置文件&#xff1a;/etc/nginx/nginx.conf (一台服务器有两个域名部署) 我们在/etc/nginx/nginx.d/下创建一个conf文件&#xff0c;这个文件会被嵌套到主配置文件当中 server { listen 80; …

《数据库应用系统实践》------ 个人作品管理系统

系列文章 《数据库应用系统实践》------ 个人作品管理系统 文章目录 系列文章一、需求分析1、系统背景2、 系统功能结构&#xff08;需包含功能结构框图和模块说明&#xff09;3&#xff0e;系统功能简介 二、概念模型设计1&#xff0e;基本要素&#xff08;符号介绍说明&…

Netty客户端与服务器端闲暇检测与心跳检测(三)

网络应用程序中普遍存在一个问题&#xff1a;连接假死&#xff0c;连接假死现象是:在某一端(服务器端|客户端)看来,底层的TCP连接已经断开,但是应用程序没有捕获到,因此会认为这个连接还存在。从TCP层面来说,只有收到四次握手数据包,或者一个RST数据包,才表示连接状态已断开; 连…

Spring练习二ssm框架整合应用

导入教程的项目&#xff0c;通过查看源码对aop面向切面编程进行理解分析 aop面向编程就像是我们给程序某些位置丢下锚点&#xff08;切入点&#xff09;以及当走到锚点时需要调用的方法&#xff08;切面&#xff09;。在程序运行的过程中&#xff0c; 一旦到达锚点&#xff0c;…

f-stack的源码编译安装

DPDK虽然能提供高性能的报文转发&#xff08;安装使用方法见DPDK的源码编译安装&#xff09;&#xff0c;但是它并没有提供对应的IP/TCP协议栈&#xff0c;所以在网络产品的某些功能场景下&#xff08;特别是涉及到需要使用TCP协议栈的情况&#xff09;&#xff0c;比如BGP邻居…

Ansible原理简介与安装篇

工作原理 1、在Ansible管理体系中&#xff0c;存在“管理节点”和“被管理节点” 2、被管理节点通常被称为”资产“ 3、在管理节点上&#xff0c;Ansible将AdHoc或PlayBook转换为python脚本。并通过SSH将这些python脚本传递到被管理服务器上。在被管理服务器上依次执行&#xf…

遥感云大数据在灾害、水体与湿地领域及GPT模型应用

近年来遥感技术得到了突飞猛进的发展&#xff0c;航天、航空、临近空间等多遥感平台不断增加&#xff0c;数据的空间、时间、光谱分辨率不断提高&#xff0c;数据量猛增&#xff0c;遥感数据已经越来越具有大数据特征。遥感大数据的出现为相关研究提供了前所未有的机遇&#xf…