数据结构中的串(String):概念、操作和实际应用

目录

一.引言

二.串的定义 

三. 串的抽象数据类型(ADT)

四. 串的存储结构

顺序存储结构

链式存储结构

五. 串模式的存储算法

KMP算法(Knuth-Morris-Pratt算法)

2.Brute-Force(暴力匹配)算法

3.Boyer-Moore算法

4.Rabin-Karp算法

六. 实际案例以及应用

七.总结


一.引言

        在计算机科学中,数据结构是一种用于组织和存储数据的方式。其中,串(String)是一种非常常见的数据结构,用于表示一组字符序列。串通常用于处理文本数据,如文本编辑器、搜索引擎、数据库系统等。在本文中,我们将深入探讨串的概念、操作以及实际应用。

二.串的定义 

        在计算机科学中,串 是由零个或多个字符组成的有限序列,是数据结构中常见的一种基本类型。它可以为空串,也可以是由字符组成的非空串。串通常用于表示文本数据,如文件内容、用户输入等,因此在算法和程序设计中占据着重要的地位。

        一个串可以是由字母、数字、符号等任意字符组成,字符的种类和个数没有固定限制。例如,"Hello, World!"、"123456"、"!@#$%" 都是串的例子。

        串的重要性在于它是对文本数据进行处理和操作的基础,涉及字符串匹配、文本编辑、编译器设计等多个领域。因此,深入理解串的特性和操作对于程序设计人员至关重要。

三. 串的抽象数据类型(ADT)

        在计算机科学中,为了方便对串进行操作和管理,可以定义串的抽象数据类型(ADT),以提供一组操作和属性来描述串的特性。下面是串的抽象数据类型的定义

// 串的抽象数据类型定义
typedef struct {
    char *str;  // 存储串的字符数组
    int length; // 串的长度
} String;

        在这个抽象数据类型中,str是一个指向字符数组的指针,用于存储串的内容,length表示串的长度。通过这个抽象数据类型,我们可以方便地对串进行创建、销毁、获取长度等操作,从而实现对串的高效管理和处理。

四. 串的存储结构

        串的存储结构是指如何在计算机内存中存储串的数据以及相关信息。常见的存储结构有两种:顺序存储结构和链式存储结构。下面分别介绍这两种存储方式,并给出相应的代码实现。

顺序存储结构

        顺序存储结构是将串中的字符顺序地存放在一段连续的存储空间中,通常是字符数组。下面是顺序存储结构的定义:

#define MAX_SIZE 100 // 假设串的最大长度为100

typedef struct {
    char str[MAX_SIZE]; // 用字符数组存储串
    int length;         // 串的长度
} SeqString;

        在顺序存储结构中,str是一个字符数组,用于存储串的内容,length表示串的长度。通过这种方式,可以直接访问串中的任意字符,并且可以快速获取串的长度。

链式存储结构

        链式存储结构是通过链表来存储串的字符,每个节点存储一个字符,并且通过指针将各个节点连接起来。下面是链式存储结构的定义:

typedef struct LNode {
    char data;
    struct LNode *next;
} LNode;

typedef struct {
    LNode *head; // 头指针
    int length;  // 串的长度
} LinkString;

        在链式存储结构中,每个节点中的data存储一个字符,next指针指向下一个节点。头指针head指向链表的第一个节点,通过遍历链表可以获取串的全部字符。链式存储结构适合于不确定串长度的情况,但需要额外的指针开销。

五. 串模式的存储算法

        串模式的存储算法主要用于实现串的模式匹配,下面是串模式常用的几种存储算法:

1.KMP算法(Knuth-Morris-Pratt算法)

  • KMP算法是一种高效的字符串匹配算法,通过预处理模式串,利用模式串中的信息在匹配失败时尽可能地减少比较次数,从而提高匹配效率。
  • KMP算法的核心思想是利用模式串内部的信息,在匹配失败时根据已匹配的部分,选择合适的位置进行模式串的移动。
  • 算法的关键在于部分匹配表(Next数组),它记录了模式串每个位置之前的子串中有多大长度的相同前缀和后缀。
  • 匹配过程中,当文本串的某个字符与模式串的对应字符不匹配时,利用Next数组确定模式串的移动位置,从而减少不必要的比较。

特点与应用:

  • KMP算法的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。在实际应用中,KMP算法通常比暴力匹配算法具有更高的效率。
  • 由于其高效的匹配速度,KMP算法被广泛应用于字符串匹配、文本搜索、编译器设计等领域。
  • 在处理大规模文本数据时,KMP算法能够提供更快速和可靠的匹配方案,因此被视为处理字符串匹配问题的重要工具之一。

下面是KMP算法的核心部分:

void getNext(char *pattern, int next[]) {
    int len = strlen(pattern);
    next[0] = -1;
    int k = -1, j = 0;
    while (j < len - 1) {
        if (k == -1 || pattern[j] == pattern[k]) {
            ++k;
            ++j;
            next[j] = k;
        } else {
            k = next[k];
        }
    }
}

int KMP(char *text, char *pattern) {
    int len1 = strlen(text);
    int len2 = strlen(pattern);
    int i = 0, j = 0;
    int *next = (int *)malloc(sizeof(int) * len2);
    getNext(pattern, next);
    while (i < len1 && j < len2) {
        if (j == -1 || text[i] == pattern[j]) {
            ++i;
            ++j;
        } else {
            j = next[j];
        }
    }
    free(next);
    if (j == len2) {
        return i - j; // 匹配成功,返回模式串在文本串中的起始位置
    } else {
        return -1; // 匹配失败
    }
}

2.Brute-Force(暴力匹配)算法

  • 暴力匹配算法是一种简单直接的字符串匹配算法,也称为朴素字符串匹配算法。它的基本思想是从文本串的第一个字符开始,与模式串进行逐个字符比较,若匹配失败,则将模式串右移一位,继续比较,直到找到匹配或者文本串被遍历完为止。
  • 该算法的时间复杂度为O(n*m),其中n为文本串的长度,m为模式串的长度。

以下是暴力匹配算法的核心部分的伪代码表示:

BruteForceSearch(text, pattern):
    n = 文本串的长度
    m = 模式串的长度
    
    for i from 0 to n - m do:
        j = 0
        while j < m 且 text[i + j] == pattern[j] do:
            j = j + 1
        
        if j == m:  # 找到了匹配
            返回 i  # 返回匹配位置的起始下标
    
返回 -1  # 没有找到匹配

3.Boyer-Moore算法

  • Boyer-Moore算法是一种高效的字符串匹配算法,其核心思想是利用模式串中的字符出现位置的信息,在匹配失败时尽可能地跳过多的字符,从而减少比较次数。
  • 该算法包含两个启发式规则:坏字符规则和好后缀规则,通过这两个规则可以确定每次匹配失败时模式串的移动位置。
  • Boyer-Moore算法的时间复杂度在最坏情况下为O(n*m),但通常情况下具有线性时间复杂度。

以下是Boyer-Moore算法的核心部分的伪代码表示:

BoyerMooreSearch(text, pattern):
    n = 文本串的长度
    m = 模式串的长度
    lastOccurrence = 记录模式串中每个字符最后出现位置的数组
    suffix = 计算模式串的后缀数组
    prefix = 计算模式串的前缀数组
    
    初始化lastOccurrence数组,记录模式串中每个字符最后出现的位置
    初始化suffix数组,计算模式串的后缀数组
    初始化prefix数组,计算模式串的前缀数组
    
    i = m - 1  # 文本串中与模式串最后一个字符对齐的位置
    j = m - 1  # 模式串中最后一个字符的下标
    
    while i < n do:
        if text[i] == pattern[j]:  # 逐个字符比较
            if j == 0:  # 如果模式串已经比较完毕,说明找到了匹配
                返回 i  # 返回匹配位置的起始下标
            else:
                i = i - 1  # 继续向前比较
                j = j - 1
        else:
            badCharacterShift = j - lastOccurrence[text[i]]  # 坏字符规则计算位移
            goodSuffixShift = calculateGoodSuffixShift(j, suffix, prefix)  # 好后缀规则计算位移
            i = i + max(badCharacterShift, goodSuffixShift)  # 取较大的位移
            j = m - 1  # 重新比较模式串的最后一个字符
    
    返回 -1  # 没有找到匹配


calculateGoodSuffixShift(j, suffix, prefix):
    k = m - 1 - j  # 好后缀的长度
    if suffix[k] != -1:  # 如果找到匹配的好后缀
        return j - suffix[k] + 1
    else:  # 如果找不到匹配的好后缀
        for r from j + 2 to m - 1:
            if prefix[m - r]:
                return r
    return m  # # 如果没有好后缀匹配,则移动整个模式串长度

4.Rabin-Karp算法

  • Rabin-Karp算法是一种基于哈希的字符串匹配算法,它通过对文本串和模式串进行哈希计算,然后比较它们的哈希值来判断是否匹配。
  • 该算法的优势在于可以在O(n+m)的时间复杂度内完成匹配,且在某些情况下比其他算法更加高效。
  • 然而,Rabin-Karp算法的实现涉及到哈希函数的设计和哈希冲突的处理,因此需要谨慎选择哈希函数以及解决哈希冲突的方法。

以下是Rabin-Karp算法的核心部分的伪代码表示:

RabinKarpSearch(text, pattern):
    n = 文本串的长度
    m = 模式串的长度
    p = 一个较大的素数,用于哈希计算
    
    textHash = 计算文本串text[0:m]的哈希值
    patternHash = 计算模式串pattern的哈希值
    
    for i from 0 to n - m do:
        if textHash == patternHash 且 text[i:i+m] == pattern:  # 判断哈希值相等且子串相等
            返回 i  # 返回匹配位置的起始下标
        
        if i < n - m:  # 更新下一次文本串的哈希值
            textHash = 重新计算text[i+1:i+m+1]的哈希值
    
    返回 -1  # 没有找到匹配

六. 实际案例以及应用

        串在计算机科学中有着广泛的应用,比如字符串匹配、文本编辑器、编译器等领域。

下面我们以文本编辑器为例进行说明。

首先,我们需要定义一些常量和全局变量:

#define MAX_LENGTH 1000

char text[MAX_LENGTH];
int length = 0;

MAX_LENGTH 是文本的最大长度,text 是用于存储文本的字符数组,length 是当前文本的长度。

然后,我们定义一些函数来实现文本编辑器的功能:

void insert(int pos, char *str) {
    int len = strlen(str);
    if (length + len > MAX_LENGTH) {
        printf("Error: Text is too long\n");
        return;
    }
    for (int i = length; i >= pos; i--) {
        text[i + len] = text[i];
    }
    for (int i = 0; i < len; i++) {
        text[pos + i] = str[i];
    }
    length += len;
}

void delete(int pos, int len) {
    if (pos + len > length) {
        printf("Error: Invalid position or length\n");
        return;
    }
    for (int i = pos; i < length - len; i++) {
        text[i] = text[i + len];
    }
    length -= len;
}

void replace(int pos, int len, char *str) {
    delete(pos, len);
    insert(pos, str);
}

int find(char *str) {
    int i, j, k;
    for (i = 0; i <= length - strlen(str); i++) {
        for (j = i, k = 0; text[j] == str[k]; j++, k++) {
            if (str[k + 1] == '\0') {
                return i;
            }
        }
    }
    return -1;
}

        insert 函数用于在指定位置插入一个字符串,delete 函数用于删除指定位置的字符串,replace 函数用于替换指定位置的字符串,find 函数用于查找指定的字符串。

        最后,我们编写主函数来测试这些函数:

int main() {
    insert(0, "Hello, ");
    insert(7, "World!");
    printf("Text: %s\n", text);

    replace(7, 5, "Dear");
    printf("Text: %s\n", text);

    int pos = find("Dear");
    if (pos != -1) {
        printf("Substring 'Dear' found at position %d\n", pos);
    } else {
        printf("Substring 'Dear' not found\n");
    }

    return 0;
}
运行这段代码,你将看到以下输出:

Text: Hello, World!
Text: Hello, Dear!
Substring 'Dear' found at position 7

七.总结

        串是一种非常常见的数据结构,用于表示一组字符序列。它支持多种操作,如插入、删除、替换、查找、比较和连接。串在现实生活中有许多应用,如文本编辑器、搜索引擎、数据库系统和编译器等。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Godot4.2】有序和无序列表函数库 - myList

概述 在打印输出或其他地方可能需要构建有序或无序列表。本质就是构造和维护一个纯文本数组。并用格式化文本形式&#xff0c;输出带序号或前缀字符的多行文本。 为此我专门设计了一个类myList&#xff0c;来完成这项任务。 代码 以下是myList类的完整代码&#xff1a; # …

AI对决:谷歌 VS 微软,谁更赚钱|TodayAI

尽管Alphabet和微软都发布了强劲的季度财报&#xff0c;显示两家科技巨头均超越了销售和利润的预期&#xff0c;但在生成式人工智能&#xff08;AI&#xff09;领域的投资回报方面&#xff0c;它们展现了不同的情况。Alphabet的CEO桑达尔皮查伊表示&#xff0c;他对Google通过出…

【Win】PsPing:深入网络性能测试与故障排查

在维护 Azure 虚拟机的过程中&#xff0c;可能会遇到一些网络连通性的问题。例如&#xff0c;当您尝试从个人 PC 上 ping 虚拟机的公网 IP 地址时&#xff0c;可能会发现出现 “Request time out” 的信息&#xff0c;导致无法 ping 通。这种情况的发生&#xff0c;通常是因为在…

【C++打怪之路Lv3】-- 类和对象(上)

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文(平均质量分82)&#…

IDEA上配置Maven环境

1.选择IDEA中的Setting 2.搜索maven 3.设置IDEA使用本地安装的Maven&#xff0c;并修改配置文件路径 配置文件&#xff0c;本地仓库&#xff0c;阿里云仓库配置及路径教程 在IDEA上配置完成。

Java学习路线及自我规划

荒废了一段时间&#xff0c;这段时间的总结开始了JavaWeb的学习但是困难重重&#xff0c;例如Maven&#xff0c;Vue的路由等&#xff0c;所以我反省了一段时间&#xff0c;因为基础薄弱&#xff0c;加之学习的资源是速成视频&#xff0c;导致大厦将倾的局面&#xff08;也算不上…

Golang | Leetcode Golang题解之第52题N皇后II

题目&#xff1a; 题解&#xff1a; func totalNQueens(n int) (ans int) {columns : make([]bool, n) // 列上是否有皇后diagonals1 : make([]bool, 2*n-1) // 左上到右下是否有皇后diagonals2 : make([]bool, 2*n-1) // 右上到左下是否有皇后var backtrack func(int)…

使用预训练模型构建自己的深度学习模型(迁移学习)

在深度学习的实际应用中&#xff0c;很少会去从头训练一个网络&#xff0c;尤其是当没有大量数据的时候。即便拥有大量数据&#xff0c;从头训练一个网络也很耗时&#xff0c;因为在大数据集上所构建的网络通常模型参数量很大&#xff0c;训练成本大。所以在构建深度学习应用时…

【redis】Redis数据类型(二)Hash类型

目录 Hash类型介绍特性hash 的内部编码方式/底层结构hashtableziplistlistpack 适用场景举例 常用命令hset示例 hsetnx示例&#xff1a; hmset示例 hget示例 hmget示例 hgetall示例 hdel示例 hlen示例 hexists示例 hincrby示例 hincrbyfloat示例 hkeys示例 hvals示例 Hash类型介…

VS2019编译OSG3.7.0+OSGEarth3.3+OSGQt5.15.2时遇到的问题及解决方法

注:本次编译以文章《VS2019编译OSG3.7.0+OSGEarth3.3+OSGQt》为基础搜集资料并进行编译 一 OSG编译 1.Osg3.7.0编译中,cmake阶段按照文章步骤即可。 2.另外,还需要对以下三项进行设置,参照《OSG-OpenSceneGraph在WIN10与VS2022下的部署(OSG3.6.5+VS2022+Win10_x64)个…

RustGUI学习(iced)之小部件(二):如何使用滑动条部件

前言 本专栏是学习Rust的GUI库iced的合集&#xff0c;将介绍iced涉及的各个小部件分别介绍&#xff0c;最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个&#xff0c;目前处于发展中&#xff08;即版本可能会改变&#xff09;&#xff0c;本专栏基于版本0.12.1. 概述…

mybatis基本使用

文章目录 1. mybatis2. 基本使用(1) maven坐标(2) 配置文件编写(3) 数据库操作(4) 注解查询 2. 基本配置(1) 读取外部配置文件(2) mapper映射 3. 映射文件查询删除/修改/新增 动态sql 1. mybatis MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高…

CSS盒子模型(如果想知道CSS有关盒子模型的知识点,那么只看这一篇就足够了!)

前言&#xff1a;在网页制作的时候&#xff0c;我们需要将网页中的元素放在指定的位置&#xff0c;那么我们如何将元素放到指定的位置上呢&#xff1f;这时候我们就需要了解盒子模型。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSD…

sCrypt全新上线RUNES功能

sCrypt智能合约平台全新上线一键etch/mint RUNES功能&#xff01; 请访问 https://runes.scrypt.io/ 或点击阅读原文体验&#xff01; 关于sCrypt sCrypt是BSV区块链上的一种智能合约高级语言。比特币使用基于堆栈的Script语言来支持智能合约&#xff0c;但是用原生Script编…

网络靶场实战-物联网安全Unicorn框架初探

背景 Unicorn 是一款基于 QEMU 的快速 CPU 模拟器框架&#xff0c;可以模拟多种体系结构的指令集&#xff0c;包括 ARM、MIPS、PowerPC、SPARC 和 x86 等。Unicorn使我们可以更好地关注 CPU 操作, 忽略机器设备的差异。它能够在虚拟内存中加载和运行二进制代码&#xff0c;并提…

密码加密案例

文章目录 描述思路错误关于增强for循环改变不了数组的值这一现象的疑问代码反思 描述 思路错误 应该是将其放入数组&#xff0c;而不是单纯的读到&#xff0c;因为你要对每一位数字进行操作 关于增强for循环改变不了数组的值这一现象的疑问 我们尝试使用增强for循环 键盘输…

uniapp使用地图开发app

使用uniapp开发app中使用到地图的坑&#xff1a; 1、简单使用地图的功能比较简单&#xff0c;仅使用到地图选点和定位功能&#xff1a;&#xff08;其中问题集中在uni.chooseLocation中&#xff09;下面是api官网地址 uni.getLocation(OBJECT) | uni-app官网 官方建议app端使…

迁移学习基础知识

简介 使用迁移学习的优势&#xff1a; 1、能够快速的训练出一个理想的结果 2、当数据集较小时也能训练出理想的效果。 注意&#xff1a;在使用别人预训练的参数模型时&#xff0c;要注意别人的预处理方式。 原理&#xff1a; 对于浅层的网络结构&#xff0c;他们学习到的…

视频批量剪辑新纪元:轻松调整音频采样率,一键实现高效视频处理!

视频剪辑已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;面对大量的视频文件&#xff0c;如何高效地进行批量剪辑&#xff0c;同时又能轻松调整音频采样率&#xff0c;成为了许多视频制作人员、自媒体从业者、教育者和学生的共同需求。 第一步&#xff0c;进入…

[C++基础学习]----02-C++运算符详解

前言 C中的运算符用于执行各种数学或逻辑运算。下面是一些常见的C运算符及其详细说明&#xff1a;下面详细解释一些常见的C运算符类型&#xff0c;包括其原理和使用方法。 正文 01-运算符简介 算术运算符&#xff1a; a、加法运算符&#xff08;&#xff09;&#xff1a;对两个…
最新文章