[从今天开始修炼数据结构]基本概念

[从今天开始修炼数据结构]线性表及其实现以及实现有Itertor的ArrayList和LinkedList

[从今天开始修炼数据结构]栈、斐波那契数列、逆波兰四则运算的实现

[从今天开始修炼数据结构]队列、循环队列、PriorityQueue的原理及实现

一、什么是串?

  串是羊肉牛肉等用铁签穿过的食物,常碳烤、油炸。不对,错了…… 串String是由零个或多个字符组成的有限序列,又名字符串。

二、串的比较

  在计算机中串的大小取决于它们挨个字母的字典顺序,比如“silly”和“stupid”,首字母s相同,比较第二个字母,“i” < “t” 所以“silly“<“stupid”。如果像”stu“和”stud“比较,那么因为多了一个字母”d“,则”stud“更大。

  事实上,串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。

  计算机中常用字符是使用标准的ASCII编码,由七位二进制数表示一个字符,总共128个。后来多了一些特殊字符,发现128个不够用,就扩展为8位二进制数,表示256个字符。然而世界上那么多种语言和特殊符号,256个字符显然是不够的,因此后来就有了Unicode编码,由16为二进制数表示一个字符,这样就可以表示大约6.5万个字符了,当然,为了兼容,Unicode的前256个字符和ASCII的是完全相同的。

  对于中文,还有GBK,UTF-8等。

三、串的抽象数据类型

  串的存储结构与线性表相同,分为两种。

  1,顺序存储结构

  用一组地址连续的存储单元来存储串中的字符序列(Java中的String底层就是一个char[]),C语言中用malloc函数从堆中动态分配空间~

  2,链式存储结构

  链是存储与线性表的链式存储类似,不过每个元素数据只是一个字符,一个节点对应一个字符的话就会浪费很大的空间。因此一个节点可以存放多个字符,若最后一个结点没有占满,可以用”#“或其他非串值字符补全。

  这里一个节点存多少个字符才合适显得十分重要,这会直接影响着串处理的效率,需要根据实际情况做出选择。但总体来说除了在连接串与串的操作时有一定方便之外,总的来说不灵活,性能也不如顺序存储结构好。

四、朴素的模式匹配算法

  朴素模式匹配就是对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做小循环,直到匹配成功或全部遍历完成为止。

  简单实现如下:

/*
外层控制主串,内层控制匹配串
如果内层匹配到最后一个位置了还没有break,则说明匹配成功
*/
public class IndexDemo {
public static int index(String main, String arg){
int index = -1;
for (int i = 0; i < (main.length() - arg.length() + 1); i++){
for (int j = 0; j < arg.length(); j++){
if (main.charAt(i + j) != arg.charAt(j)){
break;
}else if (j == arg.length()-1){
index = i;
}
}
}
if (index == -1){
return -1;
}
return index;
}
}

上面这种写法循环嵌套复杂度有点太高了,下面换成一个循环+角标回溯的方法再写一个

public class IndexDemo {
public static int index(String main, String arg, int pos){
int i = pos; //控制main的角标
int j = 0; //控制arg的角标
while(i < main.length() && j < arg.length()){
if (main.charAt(i) == arg.charAt(j)){
i++;
j++;
}else{
i = i - j + 1;
j = 0;
}
}
if (j >= arg.length()){
return i - j;
}else {
return -1;
}
}
}

这种算法简单好理解,但是时间复杂度很高,如果说被匹配的字串恰好在最后,而且前面的子串又跟匹配串的前几位都很像,那么这个串的时间复杂度将达到O(n2)。所以我们下面来一起学习高级的模式匹配方法 —— KMP方法

五、KMP模式匹配算法

KMP这个名字并不是什么专业术语,而是发明这个算法的三个前辈名字首字母的组合。

  1,讲讲原理

    我们在匹配过程中,假如遇到下面这种情况。我们把上面主串命名为S,下面的串命名为T。

如果我们现在用的是朴素匹配算法,那么在i = j = 5时遇到了不同的字符之后,下一轮会从i = 1, j = 0开始匹配。这里我们可以知道,T的首字符与后面的字符都不相同,而之前又匹配过T的前五个字符与S的前五个字符相同,所以可得T的首字符与S的前五个字符都不相同。那么经过了第一次比较之后,我们是不是就不需要判断T的首字符与S的第2 - 5个字符是否相同了呢? 所以我们只需要比较    ①当i = 0,j = 0时, 下一次就要比较  ⑥当i = 6,j = 0时

  上面这是首字符与后面字符都不相同的情况,我们再来看首字符与后面的字符有重复的情况。

  

  S和T的前五个字符完全相同,第六个字符不同,如上图。根据刚才的经验,T的首字符与角标1,2字符都不相同,不用做判断。而T的0,1位与T的3,4位相同。

  又由第一次比较,知道T的3,4位与S的3,4位也相同,所以T的0,1与S的3,4必定相同,因而下一次比较也不需要比较T的0,1和S的3,4。所以第二次比较我们可以直接从i = 5,j = 2开始比较。如下图(打叉表示无需比较)

 对比这两个例子我们会发现,在①时,我们的 i 值也就是主串的当前位置下标是5,在朴素的模式匹配算法中,主串的i值是不断回溯来完成的,即i = 0比完之后继续比较i = 1,2,3,4,才回到5,这种回溯其实是不必要的,我们的KMP算法就是为了让这没必要的回溯不再发生而产生的。

  既然 i 不回溯,那么要考虑的变化就是 j 了。通过观察我们可以发现,我们屡屡提到T中首字符与后面字符的比较,如果有相等字符,j 值的变化就会不相同。也就是说 j 值的变化关键取决于T串的结构中是否有重复。

  我们把T串各个位置的 j 值的变化定义为一个数组next,那么next的长度就是T串的长度。于是我们可以得到下面的函数定义:

  

通过上面的函数我们来推导一下T的next数组值。

  1,T = “abcdex”

j 0 1 2 3 4 5
模式串T a b c d e x
next[j] -1 0 0 0 0 0

  2,T="abcabx"

j 0 1 2 3 4 5
模式串T a b c a b x
next[j] -1 0 0 0 1 2

   
    1)当j=0时,next[0] = -1

    2)当j=1,2,3时,next[j] = 0

    3)当j=4时,此时j由0到j-1的串是abca,前缀a和后缀a相同,因此k=1(算法是由)因此next[4] = 1;

    4)当j=5时,此时j由0到j-1的串时abcab,前缀ab和后缀ab相同,因此可得k=2,所以next[5] = 2

  3,T=“aaaaaaaaab”

j 0 1 2 3 4 5 6 7 8
模式串T a a a a a a a a b
next[j] -1 0 1 2 3 4 5 6 7

    1)当j=0时,next[0] = -1

    2)当j=1时,next[1]=0

    3)当j=2时,j由0到j-1的串是aa,前缀a与后缀a同。next[2]=1;

    4)当j=3时,j由0到j-1的串是aaa,前缀aa与后缀aa同,可推k=2,所以next[3] = 2

    5)……

    6)当j=8时,j由0到j-1的串是“aaaaaaaa”,前缀aaaaaaa和后缀aaaaaaa同,,k=7所以next[8] = 7

总结:next[]数组的计算方法就是递推来的.是根据以下三条性质写出的代码

性质I:

性质II:若,则有

性质III:若,则有,且。    //这三条性质转自下面大佬2的博客

2,说完原理,下面我们来实现一下KMP算法

  看到这你也许一头雾水,我也发现我貌似水平有限,不一定能让大家明白我所讲的KMP算法,所以贴上大佬博客

  https://www.cnblogs.com/SYCstudio/p/7194315.html

  https://blog.csdn.net/wg4oma28/article/details/80115137     大佬1讲匹配过程比较清楚,大佬2讲求解next数组的代码讲解的比较清楚

  下面是KMP的实现代码

public class KMPDemo {
public static int index_KMP(String main, String arg) {
int i = 0;
int j = 0;
int[] next = getNext(arg);
while (i < main.length() && j < arg.length()) {
if (j == -1 ||
main.charAt(i) == arg.charAt(j)) {
i++;
j++;
} else {
j = next[j];
}
}
if (j >= arg.length()) {
return i - j;
} else {
return -1;
}
} private static int[] getNext(String arg) {
int[] next = new int[arg.length() + 1];
next[0] = -1;
int i = 0;//模式串中当前匹配到的位置的下标值
int j = -1;//子串匹配前缀最后一个位置的下标,-1表示没有子串,即已经回退到了0位置
while (i < arg.length()) {
if (j == -1 || arg.charAt(i) == arg.charAt(j)) {
i++;
j++;
next[i] = j;
} else {//若上一轮匹配到的前j个元素加上这轮新来的一个元素现在不能满足这么长的前缀等于后缀,则找到之前求得的next[j],说明当前满足的前缀要变短才能匹配。
j = next[j];
}
}
return next;
}
}

上面的i,j等注释,是我个人理解,如果有误,请各位一定指出!!!!感激不尽!!!!

六、KMP模式匹配算法的改进

在KMP算法中,我们当遇到有j位置匹配不上的情况时,要去查next[j];如果再匹配不上,再查next[next[j]],这样会浪费一些时间。那我们是不是可以优化一下next[]数组?

  1,原理:如果pj = pnext[j],就让j = next[j],跳过一个无用的比较。

  代码如下

  

    private static int[] getNextval(String arg) {
int[] nextval = new int[arg.length() + 1];
nextval[0] = -1;
int i = 0;//模式串中当前匹配到的位置的下标值
int j = -1;//子串匹配前缀最后一个位置的下一个元素的下标
while (i < arg.length()) {
if (j == -1 || arg.charAt(i) == arg.charAt(j)) {
i++;
j++;
if (arg.charAt(i) != arg.charAt(j)){
nextval[i] = j;
}else {
nextval[i] = nextval[j];
}
} else {//若上一轮匹配到的前j个元素加上这轮新来的一个元素现在不能满足这么长的前缀等于后缀,则找到之前求得的next[j],说明当前满足的前缀要变短才能匹配。
j = nextval[j];
}
}
return nextval;
}

[从今天开始修炼数据结构]串、KMP模式匹配算法的更多相关文章

  1. 数据结构- 串的模式匹配算法:BF和 KMP算法

      数据结构- 串的模式匹配算法:BF和 KMP算法  Brute-Force算法的思想 1.BF(Brute-Force)算法 Brute-Force算法的基本思想是: 1) 从目标串s 的第一个字 ...

  2. 数据结构(三)串---KMP模式匹配算法

    (一)定义 由于BF模式匹配算法的低效(有太多不必要的回溯和匹配),于是某三个前辈发表了一个模式匹配算法,可以大大避免重复遍历的情况,称之为克努特-莫里斯-普拉特算法,简称KMP算法 (二)KMP算法 ...

  3. 数据结构(三)串---KMP模式匹配算法实现及优化

    KMP算法实现 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include ...

  4. 数据结构(三)串---KMP模式匹配算法之获取next数组

    (一)获取模式串T的next数组值 1.回顾 我们所知道的KMP算法next数组的作用 next[j]表示当前模式串T的j下标对目标串S的i值失配时,我们应该使用模式串的下标为next[j]接着去和目 ...

  5. 【Java】 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)

    本文根据<大话数据结构>一书,实现了Java版的串的朴素模式匹配算法.KMP模式匹配算法.KMP模式匹配算法的改进算法. 1.朴素的模式匹配算法 为主串和子串分别定义指针i,j. (1)当 ...

  6. 《数据结构》之串的模式匹配算法——KMP算法

    //串的模式匹配算法 //KMP算法,时间复杂度为O(n+m) #include <iostream> #include <string> #include <cstri ...

  7. 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)

    --喜欢记得关注我哟[shoshana]-- 目录 1.朴素的模式匹配算法2.KMP模式匹配算法 2.1 KMP模式匹配算法的主体思路 2.2 next[]的定义与求解 2.3 KMP完整代码 2.4 ...

  8. 【算法】串的模式匹配算法(KMP)

    串的模式匹配算法     问题:         求子串位置的定位函数如何写? int index(SString S,SString T,int pos);         给定串S,子串T,问T在 ...

  9. 串、KMP模式匹配算法

    串是由0个或者多个字符组成的有限序列,又名叫字符串. 串的比较: 串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号. 计算机中常用的ASCII编码,由8位二进制 ...

随机推荐

  1. UE4蓝图与C++交互——射击游戏中多武器系统的实现

    回顾   学习UE4已有近2周的时间,跟着数天学院"UE4游戏开发"课程的学习,已经完成了UE4蓝图方面比较基础性的学习.通过UE4蓝图的开发,我实现了类似CS的单人版射击游戏,效 ...

  2. 合并JSON对象的正确方式

    一. 前言 “JSON对象合并”是前端开发和 NodeJS 环境开发中非常常见的操作.开发者通常会通过循环遍历或一些库封装的方法或 JavaScript ECMAScript 2015 定义的 Obj ...

  3. [考试反思]1001csp-s模拟测试(b):逃离

    如你所见,b组题,除了NC乱入直奔T2抢了我一个首杀以外A层学过FFT的人都没有参加. 竞争压力很小,题又简单,所以就造就了6个AK. 然而并不计入总分,我仍然稳在第二机房. T1lyl16分钟切掉我 ...

  4. Java自动化测试框架-10 - TestNG之测试结果篇

    1.-测试结果 1.1-成功,失败和断言 测试被认为是成功的,如果它不引发任何异常完成,还是它扔的预期异常(请参阅文档expectedExceptions属性上找到的@Test注释). 您的测试方法通 ...

  5. restapi(9)- caching, akka-http 缓存

    restapi作为前后端交互的枢纽:面对大批量的前端请求,需要确保回复的及时性.使用缓存是一项有效工具.我们可以把多数前端请求的回复response存入缓存,特别是一些需要大量计算才能获取的回复值,更 ...

  6. Kubernetes3-kubectl管理Kubernetes容器平台-1

    一.简介 1.什么是kubectl kubectl前面其实已经用到了一些,它其实就是用于操作kubernetes集群的命令行接口,通过kubectl的各种命令实现各种功能 2.环境还是用上一偏文章 K ...

  7. 微擎框架商业版 V2.1.2 去后门一键安装版+去除云平台+无附带模块

    下载地址:http://dd.ma/AdVvoDu5 关注微信公众号codervip,点击公众号菜单,获取提取码! 这个是一键安装版本,所以微擎安装比较简单,不用大家手动去改数据库了,而且修复上个2. ...

  8. js中call、apply和bind到底有什么区别?

    介绍 在js中,每个函数的原型都指向Function.prototype对象(js基于原型链的继承).因此,每个函数都会有apply,call,和bind方法,这些方法继承于Function. 它们的 ...

  9. Cef 因系统时间不正常,导致页面访问空白问题

    当我们的系统时间不正常,比如设置一个日期-1999年9月9日,会引发证书问题. 系统时间不正常-IE有概率能访问 触发NavigateError事件,异常代码INET_E_INVALID_CERTIF ...

  10. hdu 1533 Going Home (KM)

    Going HomeTime Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total ...