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

[从今天开始修炼数据结构]线性表及其实现以及实现有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. 写出float x 与“零值”比较的if语句——一道面试题分析

    写出float  x 与“零值”比较的if语句 请写出 float  x 与“零值”比较的 if 语句: const float EPSINON = 0.00001; if ((x >= - E ...

  2. 我的第一个Python爬虫——谈心得

    2019年3月27日,继开学到现在以来,开了软件工程和信息系统设计,想来想去也没什么好的题目,干脆就想弄一个实用点的,于是产生了做“学生服务系统”想法.相信各大高校应该都有本校APP或超级课程表之类的 ...

  3. 【Linux】【自学笔记】docker搭建一个spring-boot程序

    写在开始    最近捣腾Linux,安装虚拟机VMware并安装了CentOS 7系统,开始研究Linux,但是无从下手,就结合工作中用到的东西一步一步研究,事实并不是那么顺利.特此开博客,记录在过程 ...

  4. 第三十八章 POSIX线程(二)

    线程属性 初始化与销毁属性 int pthread_attr_init(pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t * ...

  5. NOI1995 石子合并 [Luogu P1880]

    一道区间dp的模板题,这里主要记一下dp时环形数据的处理. 简略版:方法一:枚举分开的位置,将圈化为链,因此要做n次. 方法二:将链重复两次,即做一个2n-1长度的链,其中第i(i<=n)堆石子 ...

  6. [Usaco 2012 Feb]Cow coupons牛券:反悔型贪心

    Description Farmer  John  needs  new  cows! There  are  N  cows  for  sale (1 <= N <= 50,000), ...

  7. 来探讨一下最近面试问的ThreadLocal问题

    中高级阶段开发者出去面试,应该躲不开ThreadLocal相关问题,本文就常见问题做出一些解答,欢迎留言探讨. ThreadLocal为java并发提供了一个新的思路, 它用来存储Thread的局部变 ...

  8. C语言变量名和地址的关系【转载】//基础的东西

    原文链接:http://blog.csdn.net/ssff1/archive/2009/12/13/4998787.aspx 变量名不占空间 变量:用来标识(identify)一块内存区域,这块区域 ...

  9. 大宇java面试系列(三):Redis常见面试题

    1. Redis 是什么?都有哪些使用场景? 我们先来理解经典的CAP理论: 一致性:是指从数据层面来看的一致性. 可用性:是指从系统层面的可用性. 容错性:是指从网络层面的的容错性. 数据库逐渐从关 ...

  10. 浅析ORACLE中NVL/NVL2/DECODE/CASE WHEN的用法

    使用NVL的时候只能对值进行是否为空的判断,基本语法是NVL( 值1, ,结果2).它的功能是如果值1为空,则此函数返回结果2,不为空的话直接输出值1(如果两个参数都为空,那么还是返回空): NVL2 ...