攻克数据结构和算法——第三天:串

简介: 一般来说,计算机硬件结构反映处理数值计算需要,而计算机上非数值处理的对象,大多就是字符串数据

一,串的基本操作


串是一种特殊的线性表。


特殊性在于:线性表的数据元素限定为字符集


一般来说,计算机硬件结构反映处理数值计算需要,而计算机上非数值处理的对象,大多就是字符串数据


1,串的定义:


是由零个或多个字符组成的有限序列。


空串:是指长度n=0的串,它不包含任何字符。



空格串:是仅由一个或多个空格组成的串,长度大于等于1。



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



主串:包含子串的串相应地称为主串。



位置:字符在序列中的序号。子串在主串中的位置则以子串的第一个字符在主串中的位置来表示。



相等:两个串的长度相等,并且对应位置的字符都相等。


串和线性表的逻辑结构极为相似,区别为,串的数据对象约束为字符集。


但是在操作时却有着很大的差别,一般来说,线性表一般将单个数据作为处理对象,而串一般将串整体或者串的一部分作为操作对象。


2,串的基本操作:


1)插入


StrInsert(S, pos, T)

初始条件:串S和T存在, 1 <= pos <= StrLength(S)+1。

操作结果:在串S的下标为pos的字符之前插入串T。


2)删除


StrDelete(S, pos, len)

初始条件:串S存在, 1 <= pos <= Length(S)

且O <= len <= Length(S)- pos +1。

操作结果:从串S中删除下标为pos的字符起长度为len的子串。


3)连接


StrCat(S,T)

初始条件:串S和T存在。

操作结果:返回由S和T联接而成的新串。


4)找子串


StrIndex(S, pos, T)

初始条件:主串S和T存在,T是非空串

操作结果:若主串S中存在和串T值相同的子串,则返回它在主串

S中从第pos个字符开始第一次出现的位置;否则函数值为0。


5)串替换


StrReplace(S, T, V)

初始条件:串S,T和V均已存在, 且T是非空串。

操作结果:用V替换主串S中出现的所有与(模式串) T相等的不重叠的子串。


二,串的模式匹配


8eeb706dc8bc4c9a8f1f293d82cf1b03.png


串的模式匹配(串匹配):找子串在主串中从第pos个字符后首次出现的位置(字串的定位操作)


 08372293baa5489f88ea0d08b4f0676c.png


主串:目标串


子串:模式串


1,BF模式匹配算法(简单匹配算法)


主串S中的子串与模式串T进行比较,直到找到相同的子串为止。


如果存在相同的子串,则匹配成功,返回子串在主串S中的位置pos否则匹配不成功。


子串与模式的比较策略:从前到后依次进行比较。


de6e9c2c8d7f427687de30e461e6cc00.png


cff8cbe5387f4659ab650272598367d8.png

cce825ae5ab94c98b2003850cdc54fff.png


代码实现:


int Index(SStringS, int pos, SString T)
{ int i=pos, j=1;
    while(i <= S.len && j <=T.len)
    { 
        if(S.ch[i] == T.ch[jl)
        {     ++i;
          ++j;
        }
        else
        { 
            i= i-j+2; 
            j= 1;
        }
    }   
        if(j> T.len)
            return i- T.len;
        else
            return 0; 
    }
}


时间复杂度:o(m*n)


2,KMP匹配算法


KMP算法: KMP为(D.E.Knuth 与J.H.Morris和V.R.Pratt)同时发现的算法,因此人们称之为克努特-莫里斯-普拉特算法。


特点:在匹配的过程中,不需要回溯主串的指针i,且时间复杂度可以达到O(m+n)。


在主串中设置指示器i表示主串S中当前比较的字符;在模式串T中设置指示器j表示模式串T中当前比

较的字符。


从主串的第pos个字符起和模式串的第一个字符比较


相等:继续逐个比较后续字符i++; j++;


j==0:继续逐个比较后续字符


j==0表示当前比较的是模式串首字符且不匹配应从主串后继字符起从头比较


不相等:从主串的位置不改变和模式串的第next[j]字符比较        j=next[j];


int Index_ KMP(SString S, int pos, SString T)
{ int i= pos, j=1;
    while (i <= S.len &&j <=T.len)
    {
        if(j==0 || S.ch[i] == T.ch[j])
        {
            ++i; 
            ++j;
        }
        else
            j = next[j];
    if (j > T.len)
        return i- T.len;
    else
        return 0;
}


3,KMP算法相比于简单模式匹配算法的改进之处是什么?


KMP算法的改进之处是它对子串做了一定的处理,使得每次匹配失败后,下一次匹配开始的位置尽可能的向后移。


KMP算法中,每当一趟匹配过程中出现失配时,主串S中的i指针不需要回溯,而是利用已经得到的“部分匹配”结果,将模式串向右“滑动”尽可能远的一段距离后,继续进行比较,从而快速达到匹配结果。


我们要在已获得的信息中做到不遗漏的同时尽可能多的四配---->A子串的后缀集合与B子串的前缀集合的交集里:最长的那个元素----->使B串后移的最少且在已知信息中四配的最多


最长元素的长度:就是j指针回溯的位置


A子串的后缀集合与B子串的前缀集合的交集里---->B子串的前缀集合与它本身的后缀集合的交集


j指针回溯的位置=B子串的最长公共前后缀


通过隐藏信息:匹配失败时A串与B串存在着一段相同的子串


j指针回溯的位置只与B串有关,与A串无关


在A、B字符串匹配之前就通过B串计算回溯位置存在一个数组next里

next与B串形成映射数组存的数据next[i]就是B[1]~B[i]的最长公共前后缀的长度


求next值


B串自己与自己匹配B[1]~B[i]的前缀与它的后缀匹配后缀为主串前缀为模式串


递推的方式求出next数组


目的:对于i属于[2,m]求出B[1]~B[i]的最长公共前后缀的长度


1.如果匹配:


next[j]=j+1 , j是B串前缀的指针


也就是当前字符匹配之前的最长公共前后缀的长度这里匹配成功了,要+1


2。如果不匹配,回溯j指针,j=next[j]直到匹配成功


KMP一次比较只会产生两种结果


1.匹配成功主串的指针向前移动


2.四配败模式串整体向前移动9


前面的移动必为n次,后面的移动最多n次


所以最大的时间复杂度为2n


同理也可得


next数组构建最大时间复杂度为2m


4,next值的计算思想:


由模式串本身发现。


3cc04b7cc5c649dab28d9d401a3a86b6.png


5,为什么模式串的next值和主串无关?


每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 i指针,而是利用已经得到的“部分匹配”的结果将模式子串向右“滑动”尽可能远的一段距离后,继续进行比较。如果 ok,那么主串的指示指针不回溯!算法的时间复杂度只和子串有关


6,


next值的计算仅和模式串本身有关


next[ ]是对模式串p进行预处理后的查询表


预处理也就是在模式p和目标进行匹配操作之前进行的操作对p而言,它并不关心要匹配的堤什么样子的,它首先要做的是很好的了解自己,也就是对自身模式的了解


2d3854ede78d4a189f93e1bc2420b21f.png


这实际上也是一个匹配的过程,不同在于:主串和模式串是同一个串


代码实现:


void Get_ Next(SString T, int next[])
{ int j=1, k =0;
    next[1]=0;
    while (j < T.len )
        { if(k==0 || T.ch[j] == T.ch[k] )
            {     ++j;
                ++k;
                next[j]=k;
            }
          else
                k = next[k]; 
    }
}


7,


当Si和Tj比较后,发现两者不相等时,但Tj和Tk相等,那就意味着Si和Tk不需要进行额外的比较了,因此j位置的nextval值仍然时k,即nextval[j] = next[j]。


void Get_ Nextval(SString T, int next[ ],int nextval[ ])
{
    int j= 2, k=0;
    Get_ Next(T, next);
    nextval[1]=0;
    next[i]=0;
    while(j <= T.len )
    {
        k=next[j];
        if( T.ch[j] == T.ch[k] )
            nextval[j]=nextval[k];
        else
            nextval[j =next[ij] ;
        j++;
    }
}


模式串的长度m比主串的长度n要小很多,且计算next或nextva1函数的时间复杂度为0(m)。因此,对于整个匹配算法来说,所增加的计算next或nextval是值得的。

目录
相关文章
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
90 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
37 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
2月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
40 4
|
2月前
|
搜索推荐 算法
数据结构与算法学习十四:常用排序算法总结和对比
关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
24 0
数据结构与算法学习十四:常用排序算法总结和对比
|
2月前
|
存储 缓存 分布式计算
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
这篇文章是关于数据结构与算法的学习指南,涵盖了数据结构的分类、数据结构与算法的关系、实际编程中遇到的问题以及几个经典的算法面试题。
37 0
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
|
2月前
|
机器学习/深度学习 搜索推荐 算法
探索数据结构:初入算法之经典排序算法
探索数据结构:初入算法之经典排序算法
|
2月前
|
算法 Java 索引
数据结构与算法学习十五:常用查找算法介绍,线性排序、二分查找(折半查找)算法、差值查找算法、斐波那契(黄金分割法)查找算法
四种常用的查找算法:顺序查找、二分查找(折半查找)、插值查找和斐波那契查找,并提供了Java语言的实现代码和测试结果。
27 0
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
137 9
|
25天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
24 1
|
12天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
30 5