深入理解JAVA字符串以及KMP算法深度详解

1.字符串抽象数据类型

java 复制代码
public class test3  {
    int length()//返回串长度
    char charAt(int i )//返回第i个字符
    void setCharAt(int i,char ch)//返回第i个字符为ch
    substring(int begin,int end)//返回序号begin至end-1的子串
    concat(String s) //返回this与s串连接生成的串
    String delete(int begin,int end)//删除从begin到end-1的子串
    boolean equals(Object obj)//比较this和obj引用是否相等
    int compareTo(String s)//比较this与s串的大小,返回两者的差值
    int indexOf(String pattern)//返回首个与模式串pattern匹配的子串序号
    void removeAll(String partern)//删除所有与pattern匹配的子串
}

2.字符串和整数相互转换的方法

java 复制代码
    //将整数转化成字符串
    int number =123;
    String numberAsString=String.valueOf(number);
    String numberAsString2=Integer.toString(number);

一般都是使用valueOf方法来进行整数转换,应为valueOf可以接收多种类型的参数,比如(int,long,float,double )等,但是toString只能接收int类型的参数。

java 复制代码
    //把String转换成int型
    String s ="123";
    int i =Integer.parseInt(s);

3.字符串的顺序存储结构和实现

1.常量字符串

JAVA字符串类主要有String常量字符串类,StringBuffer变量字符串类等。这两种字符串类都采用顺序存储结构,能够存储任意长度的字符串,实现串的基本操作,并且能够识别序号越界等错误。

Strng字符串是一个类,属于引用数据类型。提供构造串对象,求串长度,取字符,求子串,连接串,比较相等,比较大小等操作。若charAt(i),subString(begin,end)方法参数的序号越界,则排除StringIndexOutOfBoundException字符串序号越界异常。

2.String类的特点

  1. String类是最终类,不能被继承
  2. String类是以常量串方式存储和实现字符串操作
  3. 声明字符数组是最终变量,串中各字符是只读的。当构造串对象时,对字符数组进行一次赋值,其后不能更改。String类只提供了取字符操作charAt(i),不提供修改字符,插入串和删除子串操作。
  4. 构造串,求子串和连接串的操作都是深拷贝,重新申请字符串占用的字符数组,

3.比较串大小

String类声明的compareTo()方法比较两个字符串大小,返回两者之间的差值,分别为以下三种情况:

1.若两字符串s1、s2相等,则s1.compareTo(s2)返回0。

2.若两字符串s1、s2不等,则从头开始依次将两串中的对应字符进行比较,当首次遇到两个不同字符时,s1.compareTo(s2)返回这两个不同字符的差值,

java 复制代码
s1.charAt(i)-s2.charAt();//i为首次遇到两个不同字符的位置

3.两个字符串s1和s2,若s1是s2的前缀子串,或s2是s1的前缀子串,则s1.compareTo(s2)返回两者长度的差值

例如,"abcde".compareTo("ab")返回3。

实现compareTo()方法如下,比较两个字符串的大小,返回两者之间的差值。

java 复制代码
public int compareTo(MyString s){
for(int i =0;i<this.value&&i<s.value.length;++){
    if(this.value[i]!=s.value[i])
         return this.value[i]-s.value[i];
     return this.values.length-s.value.length;
}
}

4.使用String串

使用String串的求子串和连接串操作,实现串的插入,删除功能。

1.插入串

设有String串s1.s2,在s1的i位置插入s2串,返回插入后的串,调用语句如下

java 复制代码
String s1 ="a",s2 ="xyz";
int i =3;
String s3 =s1.substring(0,i)+s2+s1.substring(i);
2.删除子串

设有串s,删除串s中序号从begin到end-1子串,返回删除后的子串,调用语句如下:

java 复制代码
int begin=3, end =6;
String s4 =s.substring(0,begin)+s.substring(end);
3.反转字符串的reverse的操作:
java 复制代码
public class ReverseString {
    public static String reverse(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return reverse(str.substring(1)) + str.charAt(0);
    }

    public static void main(String[] args) {
        String originalString = "Hello, World!";
        String reversedString = reverse(originalString);
        System.out.println("Original String: " + originalString);
        System.out.println("Reversed String: " + reversedString);
    }
}
//输出:
Original String: Hello, World!
Reversed String: !dlroW ,olleH

这就是使用递归实现的 reverse 方法。它以一种简洁的方式反转字符串,尽管这种方法在处理长字符串时可能会导致性能问题。对于长字符串,使用 StringBuilderStringBuffer 通常更为高效

java 复制代码
public class ReverseString {
    public static void main(String[] args) {
        String originalString = "Hello, World!";
        String reversedString = new StringBuilder(originalString).reverse().toString();
        System.out.println("Original String: " + originalString);
        System.out.println("Reversed String: " + reversedString);
    }
}

字符的模式串匹配

BF模式算法匹配模式

设有两个串:目标串target和模式串pattern,在target目标串中查找与pattern模式串相等的一个子串并确定改子串位置的操作称为串的模式匹配。两个子串相等是指,各对应字符相同且长度相同。匹配结果有两种:如果target中存在与pattern相等的子串,则匹配成功,获得该匹配子串在target中的位置,否则匹配失败。

KMP模式匹配算法

**KMP算法,全称Knuth-Morris-Pratt字符串搜索算法,是一种高效的字符串搜索算法。**它由Donald Knuth、Vaughan Pratt和James H. Morris在1977年共同发明。KMP算法的核心思想是利用部分匹配表(也称为前缀函数或部分匹配表)来避免在文本中重复搜索已经匹配过的字符。

本篇以主串为ababcabcacbab,模式串为abcac为例

先了解一些基本概念:

  1. 前缀:除最后一个字符意外,字符串的所有头部子串。
  2. 后缀:除第一个字符以外,字符串的所有的尾部子串。

目的:需要找到的是每个子串前缀和后缀相等的最长的前缀和后缀长度。

下面是一个例子:

|-------|---------------|----------------|
| 子串 | 前缀 | 后缀 |
| a | 无 | 无 |
| ab | a | b |
| abc | ab,a | bc,c |
| abca | abc,ab,a | bca,ca,a |
| abcac | abca,abc,ab,a | bcac,caca,ac,c |

所以,字符串abcac的最长相等前后端长度是00010,将这个长度写成数组形式,得到对应的部分匹配值[0,0,0,1,0]

模拟匹配过程

我们将模拟abcac 和主串ababcabcacba进行匹配

第一趟:

主串指针len=2,模式串指针i=2时,模式串的c和主串的a匹配失败。已经匹配的字符串是ab,查看前缀表,prefix[1]=0,说明ab前缀和后缀没有相等的,所以下一趟模式串要退回到第一个字符重新比较。

|-----|---|---|-------|---|---|---|---|---|---|---|----|----|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 主串 | a | b | a | b | c | a | b | c | a | c | b | a |
| 模式串 | a | b | c | | | | | | | | | |

第二趟:

在第二次匹配之前,子串匹配索引j直接跳到第一匹配的相同前缀串的最长匹配长度的索引位置上即j=2,主串指针len=6,模式串指针i=4时,模式串的c和主串b匹配失败。已经匹配的字符串是abca前缀和后缀有一个字符相等,所以下一趟模式串要退回到第二个字符开始重新比较,也就是退回到模式串下标为1的位置

|-----|---|---|---|---|---|---|-------|---|---|---|----|----|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 主串 | a | b | a | b | c | a | b | c | a | c | b | a |
| 模式串 | | | a | b | c | a | c | | | | | |

第三趟:

主串指针len=6,模式串指针i=0时,模式串的a和主串b匹配失败。查看前缀表,prefix[0]=0,说明前缀和后缀没有相等的,因为当前与主串比较的就是模式串的第一个字符,所以,将主串移动到下一个位置,与模式串的第一个字符进行比较。

|-----|---|---|---|---|---|---|---|---|---|---|----|----|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 主串 | a | b | a | b | c | a | b | c | a | c | b | a |
| 模式串 | | | | | | a | b | c | a | c | | |

模式串全部比较完成,匹配成功,整个匹配过程中,主串始终没有回退,所以KMP算法的时间复杂度是O(m+n)。

next数组

在KMP算法中,next数组用于记录模式串中每个位子的最长相同的前缀和后缀的长度

next数组计算可以避免在搜索过程中的无效比较,因为如果当前字符不匹配,算法可以利用next数组直接跳到下一个可能匹配位置,而不是回到文本的最早位置。那么next是怎么计算的呢?

以abcabc为例:

|------------|----|---|---|---|---|---|
| j | 0 | 1 | 2 | 3 | 4 | 5 |
| 模式串 | a | b | c | a | b | c |
| 最长相同前后子串长度 | -1 | 0 | 0 | 0 | 1 | 2 |

用abcac为讲解prefix与next之间的关系:

|------|----|---|---|---|---|
| 下标 | 0 | 1 | 2 | 3 | 4 |
| 字符串 | a | b | c | a | c |
| 前缀表 | 0 | 0 | 0 | 1 | 0 |
| next | -1 | 0 | 0 | 0 | 1 |

将前缀整体右移一位,然后将空出来的第一位用-1补充,就得到了next数组。

这样子,当模式串和主串匹配失败的时候,直接查看当前匹配失败的字符的前缀表就可以了,而不是查看匹配失败字符前一个字符的前缀表了。

还是以字符串abcac与主串ababcabcacba匹配为例:

当第一趟匹配失败的时候,主串指针len=2,模式串指针i=2时,模式串的c和主串a匹配失败。查看前缀表,prefix(2)=0,说明ab前缀和后缀没有相等的,所以下一趟模式串要退回到第一个字符重新比较,也就是退到模式串pattern的下标0的位置

|-----|---|---|-------|---|---|---|---|---|---|---|----|----|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 主串 | a | b | a | b | c | a | b | c | a | c | b | a |
| 模式串 | a | b | c | | | | | | | | | |

这里是从-1开始存储和计算next数组的没所以此时next数组的含义是指:当模式串的第i个字符与主串发生匹配失败时,就退回到模式串的next[i]重新与主串进行匹配。当然可以从0开始存储和计算next数组的。

|------|---|---|---|---|---|
| 下标 | 0 | 1 | 2 | 3 | 4 |
| 字符串 | a | b | c | a | c |
| next | 0 | 1 | 1 | 1 | 2 |

KMP算法的代码实现
java 复制代码
public class KMPAlgorithm {
    private int[] computeTemporaryArray(char pattern[]){
        int[] lps =new int[pattern.length];//创建一个名为lps的整数数组,其长度与pattern数组相同。这个数组将用于存储最长公共前后缀的长度
        int index =0;//用于在lps数组中跟踪当前的最长公共前后缀的长度。
        for (int i =1;i<pattern.length;i++){//开始一个循环,从数组的第二个元素开始(索引为1),直到数组的末尾。
            if (pattern[i]==pattern[index]){
                lps[i]=index+1;
                index++;
                i++;
            }else{
                if (index!=0){
                    index =lps[index-1];
                }else{
                    lps[i]=0;
                    i++;
                }
            }
        }
        return lps;
    }
    public boolean KMP(char[] text,char[] pattern){
        int[] lps =computeTemporaryArray(pattern);
        int i =0;
        int j =0;
        while (i<text.length&&j<pattern.length){
            if (text[i]==pattern[i]){
                i++;
                j++;
            }
        }
        if (j==pattern.length){
            return true;
        }else if(i<text.length&&text[i]!=pattern[j]){
            if (j!=0){
                j=lps[j-1];
            }else{
                i=i+1;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        KMPAlgorithm kmp =new KMPAlgorithm();
        String txt ="ABABDABACDABABCABAB";
        String pat="ABABCABAB";
        char[] text =txt.toCharArray();
        char[] pattern=pat.toCharArray();
        boolean result =kmp.KMP(text,pattern);
        System.out.println("The pattern is found in the text:"+result);
    }
相关推荐
牙牙705几秒前
Centos7安装Jenkins脚本一键部署
java·servlet·jenkins
დ旧言~7 分钟前
【高阶数据结构】图论
算法·深度优先·广度优先·宽度优先·推荐算法
时光の尘7 分钟前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
paopaokaka_luck8 分钟前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
张彦峰ZYF12 分钟前
投资策略规划最优决策分析
分布式·算法·金融
以后不吃煲仔饭21 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师22 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
前端拾光者26 分钟前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
The_Ticker27 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
程序猿阿伟27 分钟前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链